flask-appbuilder 3.2.1__py3-none-any.whl → 5.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. flask_appbuilder/__init__.py +2 -3
  2. flask_appbuilder/_compat.py +0 -1
  3. flask_appbuilder/actions.py +14 -14
  4. flask_appbuilder/api/__init__.py +741 -527
  5. flask_appbuilder/api/convert.py +104 -98
  6. flask_appbuilder/api/manager.py +14 -8
  7. flask_appbuilder/api/schemas.py +12 -1
  8. flask_appbuilder/babel/manager.py +12 -16
  9. flask_appbuilder/base.py +353 -280
  10. flask_appbuilder/basemanager.py +1 -1
  11. flask_appbuilder/baseviews.py +241 -164
  12. flask_appbuilder/charts/jsontools.py +10 -10
  13. flask_appbuilder/charts/views.py +56 -60
  14. flask_appbuilder/cli.py +115 -70
  15. flask_appbuilder/const.py +52 -52
  16. flask_appbuilder/exceptions.py +67 -5
  17. flask_appbuilder/fields.py +32 -23
  18. flask_appbuilder/fieldwidgets.py +34 -27
  19. flask_appbuilder/filemanager.py +33 -45
  20. flask_appbuilder/filters.py +11 -13
  21. flask_appbuilder/forms.py +31 -35
  22. flask_appbuilder/hooks.py +90 -0
  23. flask_appbuilder/menu.py +35 -10
  24. flask_appbuilder/models/base.py +47 -57
  25. flask_appbuilder/models/decorators.py +13 -13
  26. flask_appbuilder/models/filters.py +42 -38
  27. flask_appbuilder/models/generic/__init__.py +29 -29
  28. flask_appbuilder/models/generic/filters.py +11 -3
  29. flask_appbuilder/models/generic/interface.py +1 -3
  30. flask_appbuilder/models/group.py +37 -39
  31. flask_appbuilder/models/mixins.py +22 -18
  32. flask_appbuilder/models/sqla/__init__.py +19 -72
  33. flask_appbuilder/models/sqla/base.py +24 -0
  34. flask_appbuilder/models/sqla/base_legacy.py +132 -0
  35. flask_appbuilder/models/sqla/filters.py +132 -19
  36. flask_appbuilder/models/sqla/interface.py +390 -276
  37. flask_appbuilder/security/api.py +31 -35
  38. flask_appbuilder/security/decorators.py +181 -83
  39. flask_appbuilder/security/forms.py +20 -31
  40. flask_appbuilder/security/manager.py +715 -489
  41. flask_appbuilder/security/registerviews.py +29 -112
  42. flask_appbuilder/security/schemas.py +43 -0
  43. flask_appbuilder/security/sqla/apis/__init__.py +8 -0
  44. flask_appbuilder/security/sqla/apis/group/__init__.py +1 -0
  45. flask_appbuilder/security/sqla/apis/group/api.py +227 -0
  46. flask_appbuilder/security/sqla/apis/group/schema.py +73 -0
  47. flask_appbuilder/security/sqla/apis/permission/__init__.py +1 -0
  48. flask_appbuilder/security/sqla/apis/permission/api.py +19 -0
  49. flask_appbuilder/security/sqla/apis/permission_view_menu/__init__.py +1 -0
  50. flask_appbuilder/security/sqla/apis/permission_view_menu/api.py +16 -0
  51. flask_appbuilder/security/sqla/apis/role/__init__.py +1 -0
  52. flask_appbuilder/security/sqla/apis/role/api.py +306 -0
  53. flask_appbuilder/security/sqla/apis/role/schema.py +27 -0
  54. flask_appbuilder/security/sqla/apis/user/__init__.py +1 -0
  55. flask_appbuilder/security/sqla/apis/user/api.py +292 -0
  56. flask_appbuilder/security/sqla/apis/user/schema.py +97 -0
  57. flask_appbuilder/security/sqla/apis/user/validator.py +27 -0
  58. flask_appbuilder/security/sqla/apis/view_menu/__init__.py +1 -0
  59. flask_appbuilder/security/sqla/apis/view_menu/api.py +18 -0
  60. flask_appbuilder/security/sqla/manager.py +421 -203
  61. flask_appbuilder/security/sqla/models.py +192 -57
  62. flask_appbuilder/security/utils.py +9 -0
  63. flask_appbuilder/security/views.py +232 -229
  64. flask_appbuilder/static/.DS_Store +0 -0
  65. flask_appbuilder/static/appbuilder/css/ab.css +20 -12
  66. flask_appbuilder/static/appbuilder/css/bootstrap-datepicker/bootstrap-datepicker3.min.css +7 -0
  67. flask_appbuilder/static/appbuilder/css/bootstrap.min.css.map +1 -0
  68. flask_appbuilder/static/appbuilder/css/flags/flags16.css +249 -245
  69. flask_appbuilder/static/appbuilder/css/fontawesome/all.min.css +6 -0
  70. flask_appbuilder/static/appbuilder/css/fontawesome/brands.min.css +6 -0
  71. flask_appbuilder/static/appbuilder/css/fontawesome/fontawesome.min.css +6 -0
  72. flask_appbuilder/static/appbuilder/css/fontawesome/regular.min.css +6 -0
  73. flask_appbuilder/static/appbuilder/css/fontawesome/solid.min.css +6 -0
  74. flask_appbuilder/static/appbuilder/css/fontawesome/svg-with-js.min.css +6 -0
  75. flask_appbuilder/static/appbuilder/css/fontawesome/v4-font-face.min.css +6 -0
  76. flask_appbuilder/static/appbuilder/css/fontawesome/v4-shims.min.css +6 -0
  77. flask_appbuilder/static/appbuilder/css/fontawesome/v5-font-face.min.css +6 -0
  78. flask_appbuilder/static/appbuilder/css/images/flags16.png +0 -0
  79. flask_appbuilder/static/appbuilder/css/select2/select2-bootstrap.min.css +7 -0
  80. flask_appbuilder/static/appbuilder/css/select2/select2.min.css +1 -0
  81. flask_appbuilder/static/appbuilder/css/swagger/swagger-ui.css +3 -0
  82. flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.ttf +0 -0
  83. flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.woff2 +0 -0
  84. flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.ttf +0 -0
  85. flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.woff2 +0 -0
  86. flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.ttf +0 -0
  87. flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.woff2 +0 -0
  88. flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.ttf +0 -0
  89. flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.woff2 +0 -0
  90. flask_appbuilder/static/appbuilder/js/ab.js +33 -23
  91. flask_appbuilder/static/appbuilder/js/ab_filters.js +91 -84
  92. flask_appbuilder/static/appbuilder/js/bootstrap-datepicker/bootstrap-datepicker.min.js +8 -0
  93. flask_appbuilder/static/appbuilder/js/jquery-latest.js +2 -2
  94. flask_appbuilder/static/appbuilder/js/select2/select2.min.js +2 -0
  95. flask_appbuilder/static/appbuilder/js/swagger-ui-bundle.js +3 -0
  96. flask_appbuilder/templates/appbuilder/baselib.html +9 -3
  97. flask_appbuilder/templates/appbuilder/general/lib.html +60 -34
  98. flask_appbuilder/templates/appbuilder/general/model/edit.html +1 -1
  99. flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html +1 -1
  100. flask_appbuilder/templates/appbuilder/general/model/search.html +3 -2
  101. flask_appbuilder/templates/appbuilder/general/model/show.html +1 -1
  102. flask_appbuilder/templates/appbuilder/general/model/show_cascade.html +1 -1
  103. flask_appbuilder/templates/appbuilder/general/security/login_db.html +7 -7
  104. flask_appbuilder/templates/appbuilder/general/security/login_ldap.html +5 -5
  105. flask_appbuilder/templates/appbuilder/general/security/login_oauth.html +24 -49
  106. flask_appbuilder/templates/appbuilder/general/widgets/base_list.html +2 -1
  107. flask_appbuilder/templates/appbuilder/general/widgets/chart.html +4 -2
  108. flask_appbuilder/templates/appbuilder/general/widgets/direct_chart.html +4 -3
  109. flask_appbuilder/templates/appbuilder/general/widgets/multiple_chart.html +3 -2
  110. flask_appbuilder/templates/appbuilder/general/widgets/search.html +11 -10
  111. flask_appbuilder/templates/appbuilder/init.html +37 -43
  112. flask_appbuilder/templates/appbuilder/navbar_menu.html +1 -1
  113. flask_appbuilder/templates/appbuilder/navbar_right.html +2 -2
  114. flask_appbuilder/templates/appbuilder/swagger/swagger.html +22 -19
  115. flask_appbuilder/translations/de/LC_MESSAGES/messages.mo +0 -0
  116. flask_appbuilder/translations/de/LC_MESSAGES/messages.po +305 -161
  117. flask_appbuilder/translations/fa/LC_MESSAGES/messages.mo +0 -0
  118. flask_appbuilder/translations/fa/LC_MESSAGES/messages.po +802 -0
  119. flask_appbuilder/translations/fr/LC_MESSAGES/messages.po +461 -319
  120. flask_appbuilder/translations/pt_BR/LC_MESSAGES/messages.po +650 -650
  121. flask_appbuilder/translations/ru/LC_MESSAGES/messages.po +1 -1
  122. flask_appbuilder/translations/sl/LC_MESSAGES/messages.mo +0 -0
  123. flask_appbuilder/translations/sl/LC_MESSAGES/messages.po +690 -0
  124. flask_appbuilder/translations/tr/LC_MESSAGES/messages.mo +0 -0
  125. flask_appbuilder/translations/tr/LC_MESSAGES/messages.po +1015 -0
  126. flask_appbuilder/upload.py +20 -22
  127. flask_appbuilder/urltools.py +39 -19
  128. flask_appbuilder/utils/base.py +76 -0
  129. flask_appbuilder/utils/legacy.py +33 -0
  130. flask_appbuilder/utils/limit.py +20 -0
  131. flask_appbuilder/validators.py +73 -14
  132. flask_appbuilder/views.py +75 -424
  133. flask_appbuilder/widgets.py +50 -51
  134. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/METADATA +36 -76
  135. flask_appbuilder-5.0.2.dist-info/RECORD +240 -0
  136. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/WHEEL +1 -1
  137. flask_appbuilder-5.0.2.dist-info/entry_points.txt +2 -0
  138. Flask_AppBuilder-3.2.1.dist-info/RECORD +0 -270
  139. Flask_AppBuilder-3.2.1.dist-info/entry_points.txt +0 -6
  140. flask_appbuilder/console.py +0 -426
  141. flask_appbuilder/models/mongoengine/__init__.py +0 -0
  142. flask_appbuilder/models/mongoengine/fields.py +0 -65
  143. flask_appbuilder/models/mongoengine/filters.py +0 -145
  144. flask_appbuilder/models/mongoengine/interface.py +0 -328
  145. flask_appbuilder/security/mongoengine/__init__.py +0 -0
  146. flask_appbuilder/security/mongoengine/manager.py +0 -402
  147. flask_appbuilder/security/mongoengine/models.py +0 -120
  148. flask_appbuilder/static/appbuilder/css/font-awesome.min.css +0 -4
  149. flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.css +0 -9
  150. flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.js +0 -28
  151. flask_appbuilder/static/appbuilder/fonts/FontAwesome.otf +0 -0
  152. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.eot +0 -0
  153. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.svg +0 -2671
  154. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.ttf +0 -0
  155. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff +0 -0
  156. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff2 +0 -0
  157. flask_appbuilder/static/appbuilder/img/aol.png +0 -0
  158. flask_appbuilder/static/appbuilder/img/flags/flags16.png +0 -0
  159. flask_appbuilder/static/appbuilder/img/flickr.png +0 -0
  160. flask_appbuilder/static/appbuilder/img/google.png +0 -0
  161. flask_appbuilder/static/appbuilder/img/myopenid.png +0 -0
  162. flask_appbuilder/static/appbuilder/img/yahoo.png +0 -0
  163. flask_appbuilder/static/appbuilder/js/_google_charts.js +0 -39
  164. flask_appbuilder/static/appbuilder/js/html5shiv.js +0 -8
  165. flask_appbuilder/static/appbuilder/js/respond.min.js +0 -6
  166. flask_appbuilder/static/appbuilder/select2/select2-spinner.gif +0 -0
  167. flask_appbuilder/static/appbuilder/select2/select2.css +0 -1205
  168. flask_appbuilder/static/appbuilder/select2/select2.js +0 -23
  169. flask_appbuilder/static/appbuilder/select2/select2.png +0 -0
  170. flask_appbuilder/static/appbuilder/select2/select2x2.png +0 -0
  171. flask_appbuilder/templates/appbuilder/general/security/login_oid.html +0 -129
  172. flask_appbuilder/templates/appbuilder/general/security/resetpassword.html +0 -29
  173. flask_appbuilder/tests/__init__.py +0 -0
  174. flask_appbuilder/tests/__pycache__/__init__.cpython-36.pyc +0 -0
  175. flask_appbuilder/tests/__pycache__/__init__.cpython-37.pyc +0 -0
  176. flask_appbuilder/tests/__pycache__/_test_auth_ldap.cpython-37.pyc +0 -0
  177. flask_appbuilder/tests/__pycache__/_test_auth_oauth.cpython-37.pyc +0 -0
  178. flask_appbuilder/tests/__pycache__/_test_ldapsearch.cpython-36.pyc +0 -0
  179. flask_appbuilder/tests/__pycache__/_test_oauth_registration_role.cpython-36.pyc +0 -0
  180. flask_appbuilder/tests/__pycache__/base.cpython-36.pyc +0 -0
  181. flask_appbuilder/tests/__pycache__/base.cpython-37.pyc +0 -0
  182. flask_appbuilder/tests/__pycache__/config_api.cpython-36.pyc +0 -0
  183. flask_appbuilder/tests/__pycache__/config_api.cpython-37.pyc +0 -0
  184. flask_appbuilder/tests/__pycache__/const.cpython-36.pyc +0 -0
  185. flask_appbuilder/tests/__pycache__/const.cpython-37.pyc +0 -0
  186. flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-36.pyc +0 -0
  187. flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-37.pyc +0 -0
  188. flask_appbuilder/tests/__pycache__/test_api.cpython-36.pyc +0 -0
  189. flask_appbuilder/tests/__pycache__/test_api.cpython-37.pyc +0 -0
  190. flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-36.pyc +0 -0
  191. flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-37.pyc +0 -0
  192. flask_appbuilder/tests/__pycache__/test_menu.cpython-36.pyc +0 -0
  193. flask_appbuilder/tests/__pycache__/test_menu.cpython-37.pyc +0 -0
  194. flask_appbuilder/tests/__pycache__/test_mongoengine.cpython-36.pyc +0 -0
  195. flask_appbuilder/tests/__pycache__/test_mvc.cpython-36.pyc +0 -0
  196. flask_appbuilder/tests/__pycache__/test_mvc.cpython-37.pyc +0 -0
  197. flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-36.pyc +0 -0
  198. flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-37.pyc +0 -0
  199. flask_appbuilder/tests/_test_auth_ldap.py +0 -1045
  200. flask_appbuilder/tests/_test_auth_oauth.py +0 -419
  201. flask_appbuilder/tests/_test_ldapsearch.py +0 -135
  202. flask_appbuilder/tests/_test_oauth_registration_role.py +0 -59
  203. flask_appbuilder/tests/app.db +0 -0
  204. flask_appbuilder/tests/base.py +0 -90
  205. flask_appbuilder/tests/config_api.py +0 -21
  206. flask_appbuilder/tests/const.py +0 -9
  207. flask_appbuilder/tests/mongoengine/__init__.py +0 -0
  208. flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-36.pyc +0 -0
  209. flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-37.pyc +0 -0
  210. flask_appbuilder/tests/mongoengine/__pycache__/models.cpython-36.pyc +0 -0
  211. flask_appbuilder/tests/mongoengine/models.py +0 -41
  212. flask_appbuilder/tests/sqla/__init__.py +0 -0
  213. flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-36.pyc +0 -0
  214. flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-37.pyc +0 -0
  215. flask_appbuilder/tests/sqla/__pycache__/models.cpython-36.pyc +0 -0
  216. flask_appbuilder/tests/sqla/__pycache__/models.cpython-37.pyc +0 -0
  217. flask_appbuilder/tests/sqla/models.py +0 -340
  218. flask_appbuilder/tests/test_0_fixture.py +0 -39
  219. flask_appbuilder/tests/test_api.py +0 -2790
  220. flask_appbuilder/tests/test_fab_cli.py +0 -72
  221. flask_appbuilder/tests/test_menu.py +0 -122
  222. flask_appbuilder/tests/test_mongoengine.py +0 -572
  223. flask_appbuilder/tests/test_mvc.py +0 -1710
  224. flask_appbuilder/tests/test_sqlalchemy.py +0 -24
  225. flask_appbuilder/translations/__pycache__/__init__.cpython-36.pyc +0 -0
  226. flask_appbuilder/translations/es/LC_MESSAGES/messages.po~ +0 -582
  227. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/LICENSE +0 -0
  228. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/top_level.txt +0 -0
@@ -1,2790 +0,0 @@
1
- import json
2
- import logging
3
- import os
4
-
5
- from flask_appbuilder import ModelRestApi, SQLA
6
- from flask_appbuilder.const import (
7
- API_ADD_COLUMNS_RES_KEY,
8
- API_ADD_COLUMNS_RIS_KEY,
9
- API_ADD_TITLE_RIS_KEY,
10
- API_DESCRIPTION_COLUMNS_RES_KEY,
11
- API_DESCRIPTION_COLUMNS_RIS_KEY,
12
- API_EDIT_COLUMNS_RES_KEY,
13
- API_EDIT_COLUMNS_RIS_KEY,
14
- API_EDIT_TITLE_RIS_KEY,
15
- API_FILTERS_RIS_KEY,
16
- API_LABEL_COLUMNS_RES_KEY,
17
- API_LABEL_COLUMNS_RIS_KEY,
18
- API_LIST_COLUMNS_RES_KEY,
19
- API_LIST_COLUMNS_RIS_KEY,
20
- API_LIST_TITLE_RIS_KEY,
21
- API_ORDER_COLUMNS_RIS_KEY,
22
- API_PERMISSIONS_RES_KEY,
23
- API_PERMISSIONS_RIS_KEY,
24
- API_RESULT_RES_KEY,
25
- API_SECURITY_ACCESS_TOKEN_KEY,
26
- API_SELECT_COLUMNS_RIS_KEY,
27
- API_SELECT_KEYS_RIS_KEY,
28
- API_SHOW_COLUMNS_RIS_KEY,
29
- API_SHOW_TITLE_RIS_KEY,
30
- API_URI_RIS_KEY,
31
- )
32
- from flask_appbuilder.models.sqla.filters import FilterGreater, FilterSmaller
33
- from flask_appbuilder.models.sqla.interface import SQLAInterface
34
- import prison
35
- from sqlalchemy.sql.expression import func
36
-
37
- from .base import FABTestCase
38
- from .const import (
39
- MAX_PAGE_SIZE,
40
- MODEL1_DATA_SIZE,
41
- MODEL2_DATA_SIZE,
42
- MODELOMCHILD_DATA_SIZE,
43
- PASSWORD_ADMIN,
44
- PASSWORD_READONLY,
45
- USERNAME_ADMIN,
46
- USERNAME_READONLY,
47
- )
48
- from .sqla.models import (
49
- insert_model1,
50
- insert_model2,
51
- Model1,
52
- Model2,
53
- Model4,
54
- ModelMMChild,
55
- ModelMMParent,
56
- ModelMMParentRequired,
57
- ModelOMChild,
58
- ModelOMParent,
59
- ModelWithEnums,
60
- ModelWithProperty,
61
- TmpEnum,
62
- validate_name,
63
- )
64
-
65
-
66
- log = logging.getLogger(__name__)
67
-
68
-
69
- class APICSRFTestCase(FABTestCase):
70
- def setUp(self):
71
- from flask import Flask
72
- from flask_wtf import CSRFProtect
73
- from flask_appbuilder import AppBuilder
74
-
75
- self.app = Flask(__name__)
76
- self.app.config.from_object("flask_appbuilder.tests.config_api")
77
- self.app.config["WTF_CSRF_ENABLED"] = True
78
-
79
- self.csrf = CSRFProtect(self.app)
80
- self.db = SQLA(self.app)
81
- self.appbuilder = AppBuilder(self.app, self.db.session)
82
-
83
- def test_auth_login(self):
84
- """
85
- REST Api: Test auth login CSRF
86
- """
87
- client = self.app.test_client()
88
- rv = self._login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
89
- self.assertEqual(rv.status_code, 200)
90
- assert json.loads(rv.data.decode("utf-8")).get(
91
- API_SECURITY_ACCESS_TOKEN_KEY, False
92
- )
93
-
94
-
95
- class APIDisableSecViewTestCase(FABTestCase):
96
-
97
- base_fab_endpoint = [
98
- "IndexView.index",
99
- "appbuilder.static",
100
- "static",
101
- "LocaleView.index",
102
- "UtilView.back",
103
- "MenuApi.get_menu_data",
104
- ]
105
-
106
- def setUp(self):
107
- from flask import Flask
108
- from flask_appbuilder import AppBuilder
109
-
110
- self.app = Flask(__name__)
111
- self.app.config.from_object("flask_appbuilder.tests.config_api")
112
- self.app.config["FAB_ADD_SECURITY_VIEWS"] = False
113
-
114
- self.db = SQLA(self.app)
115
- self.appbuilder = AppBuilder(self.app, self.db.session)
116
-
117
- def test_disabled_security_views(self):
118
- """
119
- REST Api: Test disabled security views
120
- """
121
- for rule in self.appbuilder.get_app.url_map.iter_rules():
122
- self.assertIn(rule.endpoint, self.base_fab_endpoint)
123
-
124
-
125
- class APITestCase(FABTestCase):
126
- def setUp(self):
127
- from flask import Flask
128
- from flask_appbuilder import AppBuilder
129
- from flask_appbuilder.models.filters import BaseFilter
130
- from flask_appbuilder.models.sqla.interface import SQLAInterface
131
- from flask_appbuilder.api import (
132
- BaseApi,
133
- ModelRestApi,
134
- protect,
135
- expose,
136
- rison,
137
- safe,
138
- )
139
-
140
- self.app = Flask(__name__)
141
- self.basedir = os.path.abspath(os.path.dirname(__file__))
142
- self.app.config.from_object("flask_appbuilder.tests.config_api")
143
- self.app.config["FAB_API_MAX_PAGE_SIZE"] = MAX_PAGE_SIZE
144
-
145
- self.db = SQLA(self.app)
146
- self.appbuilder = AppBuilder(self.app, self.db.session)
147
-
148
- rison_schema = {
149
- "type": "object",
150
- "required": ["number"],
151
- "properties": {"number": {"type": "number"}},
152
- }
153
-
154
- class Base1Api(BaseApi):
155
- @expose("/test1")
156
- @protect()
157
- @safe
158
- @rison(rison_schema)
159
- def test1(self, **kwargs):
160
- return self.response(200, message=f"{kwargs['rison']['number'] + 1}")
161
-
162
- @expose("/test2")
163
- @protect()
164
- @safe
165
- def test2(self, **kwargs):
166
- raise Exception
167
-
168
- self.appbuilder.add_api(Base1Api)
169
-
170
- class Model1Api(ModelRestApi):
171
- datamodel = SQLAInterface(Model1)
172
- list_columns = [
173
- "field_integer",
174
- "field_float",
175
- "field_string",
176
- "field_date",
177
- ]
178
- description_columns = {
179
- "field_integer": "Field Integer",
180
- "field_float": "Field Float",
181
- "field_string": "Field String",
182
- }
183
-
184
- self.model1api = Model1Api
185
- self.appbuilder.add_api(Model1Api)
186
-
187
- class CustomFilter(BaseFilter):
188
- name = "Custom Filter"
189
- arg_name = "custom_filter"
190
-
191
- def apply(self, query, value):
192
- return query.filter(
193
- ~Model1.field_string.like(value + "%"), Model1.field_integer == 1
194
- )
195
-
196
- class Model1ApiSearchFilters(ModelRestApi):
197
- datamodel = SQLAInterface(Model1)
198
- search_filters = {"field_string": [CustomFilter]}
199
-
200
- self.appbuilder.add_api(Model1ApiSearchFilters)
201
-
202
- class Model1ApiFieldsInfo(Model1Api):
203
- datamodel = SQLAInterface(Model1)
204
- add_columns = ["field_integer", "field_float", "field_string", "field_date"]
205
- edit_columns = ["field_string", "field_integer"]
206
-
207
- self.model1apifieldsinfo = Model1ApiFieldsInfo
208
- self.appbuilder.add_api(Model1ApiFieldsInfo)
209
-
210
- class Model1FuncApi(ModelRestApi):
211
- datamodel = SQLAInterface(Model1)
212
- list_columns = [
213
- "field_integer",
214
- "field_float",
215
- "field_string",
216
- "field_date",
217
- "full_concat",
218
- ]
219
- description_columns = {
220
- "field_integer": "Field Integer",
221
- "field_float": "Field Float",
222
- "field_string": "Field String",
223
- }
224
-
225
- self.model1funcapi = Model1Api
226
- self.appbuilder.add_api(Model1FuncApi)
227
-
228
- class Model1ApiExcludeCols(ModelRestApi):
229
- datamodel = SQLAInterface(Model1)
230
- list_exclude_columns = ["field_integer", "field_float", "field_date"]
231
- show_exclude_columns = list_exclude_columns
232
- edit_exclude_columns = list_exclude_columns
233
- add_exclude_columns = list_exclude_columns
234
-
235
- self.appbuilder.add_api(Model1ApiExcludeCols)
236
-
237
- class Model1ApiExcludeRoutes(ModelRestApi):
238
- datamodel = SQLAInterface(Model1)
239
- exclude_route_methods = ("info", "delete")
240
-
241
- self.appbuilder.add_api(Model1ApiExcludeRoutes)
242
-
243
- class Model1ApiOrder(ModelRestApi):
244
- datamodel = SQLAInterface(Model1)
245
- base_order = ("field_integer", "desc")
246
-
247
- self.appbuilder.add_api(Model1ApiOrder)
248
-
249
- class Model1ApiRestrictedPermissions(ModelRestApi):
250
- datamodel = SQLAInterface(Model1)
251
- base_permissions = ["can_get", "can_info"]
252
-
253
- self.appbuilder.add_api(Model1ApiRestrictedPermissions)
254
-
255
- class Model1ApiFiltered(ModelRestApi):
256
- datamodel = SQLAInterface(Model1)
257
- base_filters = [
258
- ["field_integer", FilterGreater, 2],
259
- ["field_integer", FilterSmaller, 4],
260
- ]
261
-
262
- self.appbuilder.add_api(Model1ApiFiltered)
263
-
264
- class ModelWithEnumsApi(ModelRestApi):
265
- datamodel = SQLAInterface(ModelWithEnums)
266
-
267
- self.appbuilder.add_api(ModelWithEnumsApi)
268
-
269
- class Model1BrowserLogin(ModelRestApi):
270
- datamodel = SQLAInterface(Model1)
271
- allow_browser_login = True
272
-
273
- self.appbuilder.add_api(Model1BrowserLogin)
274
-
275
- class ModelMMApi(ModelRestApi):
276
- datamodel = SQLAInterface(ModelMMParent)
277
-
278
- self.appbuilder.add_api(ModelMMApi)
279
-
280
- class ModelDottedMMApi(ModelRestApi):
281
- datamodel = SQLAInterface(ModelMMParent)
282
- list_columns = ["field_string", "children.field_integer"]
283
- show_columns = ["field_string", "children.field_integer"]
284
-
285
- self.modeldottedmmapi = ModelDottedMMApi
286
- self.appbuilder.add_api(ModelDottedMMApi)
287
-
288
- class ModelOMParentApi(ModelRestApi):
289
- datamodel = SQLAInterface(ModelOMParent)
290
-
291
- self.appbuilder.add_api(ModelOMParentApi)
292
-
293
- class ModelDottedOMParentApi(ModelRestApi):
294
- datamodel = SQLAInterface(ModelOMParent)
295
- list_columns = ["field_string", "children.field_string"]
296
- show_columns = ["field_string", "children.field_string"]
297
-
298
- self.appbuilder.add_api(ModelDottedOMParentApi)
299
-
300
- class ModelMMRequiredApi(ModelRestApi):
301
- datamodel = SQLAInterface(ModelMMParentRequired)
302
-
303
- self.appbuilder.add_api(ModelMMRequiredApi)
304
-
305
- class Model1CustomValidationApi(ModelRestApi):
306
- datamodel = SQLAInterface(Model1)
307
- validators_columns = {"field_string": validate_name}
308
-
309
- self.appbuilder.add_api(Model1CustomValidationApi)
310
-
311
- class Model2Api(ModelRestApi):
312
- datamodel = SQLAInterface(Model2)
313
- list_columns = ["group"]
314
- show_columns = ["group"]
315
-
316
- self.model2api = Model2Api
317
- self.appbuilder.add_api(Model2Api)
318
-
319
- class Model2DottedNotationApi(ModelRestApi):
320
- datamodel = SQLAInterface(Model2)
321
- list_columns = ["field_string", "group.field_string"]
322
- show_columns = list_columns
323
-
324
- self.appbuilder.add_api(Model2DottedNotationApi)
325
-
326
- class Model2ApiFilteredRelFields(ModelRestApi):
327
- datamodel = SQLAInterface(Model2)
328
- list_columns = ["group"]
329
- show_columns = ["group"]
330
- add_query_rel_fields = {
331
- "group": [
332
- ["field_integer", FilterGreater, 2],
333
- ["field_integer", FilterSmaller, 4],
334
- ]
335
- }
336
- edit_query_rel_fields = add_query_rel_fields
337
-
338
- self.model2apifilteredrelfields = Model2ApiFilteredRelFields
339
- self.appbuilder.add_api(Model2ApiFilteredRelFields)
340
-
341
- class Model2CallableColApi(ModelRestApi):
342
- datamodel = SQLAInterface(Model2)
343
- list_columns = ["field_string", "field_integer", "field_method"]
344
- show_columns = list_columns
345
-
346
- self.appbuilder.add_api(Model2CallableColApi)
347
-
348
- class Model1PermOverride(ModelRestApi):
349
- datamodel = SQLAInterface(Model1)
350
- class_permission_name = "api"
351
- method_permission_name = {
352
- "get_list": "access",
353
- "get": "access",
354
- "put": "access",
355
- "post": "access",
356
- "delete": "access",
357
- "info": "access",
358
- }
359
-
360
- self.model1permoverride = Model1PermOverride
361
- self.appbuilder.add_api(Model1PermOverride)
362
-
363
- class ModelWithPropertyApi(ModelRestApi):
364
- datamodel = SQLAInterface(ModelWithProperty)
365
- list_columns = ["field_string", "custom_property"]
366
-
367
- self.model1permoverride = ModelWithPropertyApi
368
- self.appbuilder.add_api(ModelWithPropertyApi)
369
-
370
- class Model1ApiIncludeRoutes(ModelRestApi):
371
- datamodel = SQLAInterface(Model1)
372
- include_route_methods = "get"
373
-
374
- self.appbuilder.add_api(Model1ApiIncludeRoutes)
375
-
376
- def tearDown(self):
377
- self.appbuilder.get_session.close()
378
- engine = self.db.session.get_bind(mapper=None, clause=None)
379
- engine.dispose()
380
-
381
- def test_babel(self):
382
- """
383
- REST Api: Test babel simple test
384
- """
385
- client = self.app.test_client()
386
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
387
- uri = "api/v1/model1api/?_l_=en"
388
- rv = self.auth_client_get(client, token, uri)
389
- self.assertEqual(rv.status_code, 200)
390
-
391
- def test_babel_wrong_language(self):
392
- """
393
- REST Api: Test babel with a wrong language
394
- """
395
- client = self.app.test_client()
396
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
397
- uri = "api/v1/model1api/?_l_=xx"
398
- rv = self.auth_client_get(client, token, uri)
399
- self.assertEqual(rv.status_code, 200)
400
-
401
- def test_auth_login(self):
402
- """
403
- REST Api: Test auth login
404
- """
405
- client = self.app.test_client()
406
- rv = self._login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
407
- self.assertEqual(rv.status_code, 200)
408
- assert json.loads(rv.data.decode("utf-8")).get(
409
- API_SECURITY_ACCESS_TOKEN_KEY, False
410
- )
411
-
412
- def test_auth_login_failed(self):
413
- """
414
- REST Api: Test auth login failed
415
- """
416
- client = self.app.test_client()
417
- rv = self._login(client, "fail", "fail")
418
- self.assertEqual(json.loads(rv.data), {"message": "Not authorized"})
419
- self.assertEqual(rv.status_code, 401)
420
-
421
- def test_auth_login_bad(self):
422
- """
423
- REST Api: Test auth login bad request
424
- """
425
- client = self.app.test_client()
426
- rv = client.post("api/v1/security/login", data="BADADATA")
427
- self.assertEqual(rv.status_code, 400)
428
-
429
- def test_auth_authorization_browser(self):
430
- """
431
- REST Api: Test auth with browser login
432
- """
433
- client = self.app.test_client()
434
- rv = self.browser_login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
435
- # Test access with browser login
436
- uri = "api/v1/model1browserlogin/1"
437
- rv = client.get(uri)
438
- self.assertEqual(rv.status_code, 200)
439
- # Test unauthorized access with browser login
440
- uri = "api/v1/model1api/1"
441
- rv = client.get(uri)
442
- self.assertEqual(rv.status_code, 401)
443
- # Test access wihout cookie or JWT
444
- rv = self.browser_logout(client)
445
- # Test access with browser login
446
- uri = "api/v1/model1browserlogin/1"
447
- rv = client.get(uri)
448
- self.assertEqual(rv.status_code, 401)
449
- # Test access with JWT but without cookie
450
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
451
- uri = "api/v1/model1browserlogin/1"
452
- rv = self.auth_client_get(client, token, uri)
453
- self.assertEqual(rv.status_code, 200)
454
-
455
- def test_auth_authorization(self):
456
- """
457
- REST Api: Test auth base limited authorization
458
- """
459
- client = self.app.test_client()
460
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
461
- # Test unauthorized DELETE
462
- pk = 1
463
- uri = f"api/v1/model1apirestrictedpermissions/{pk}"
464
- rv = self.auth_client_delete(client, token, uri)
465
- self.assertEqual(rv.status_code, 401)
466
- # Test unauthorized POST
467
- item = dict(
468
- field_string="test{}".format(MODEL1_DATA_SIZE + 1),
469
- field_integer=MODEL1_DATA_SIZE + 1,
470
- field_float=float(MODEL1_DATA_SIZE + 1),
471
- field_date=None,
472
- )
473
- uri = "api/v1/model1apirestrictedpermissions/"
474
- rv = self.auth_client_post(client, token, uri, item)
475
- self.assertEqual(rv.status_code, 401)
476
- # Test authorized GET
477
- uri = f"api/v1/model1apirestrictedpermissions/{pk}"
478
- rv = self.auth_client_get(client, token, uri)
479
- self.assertEqual(rv.status_code, 200)
480
-
481
- def test_auth_builtin_roles(self):
482
- """
483
- REST Api: Test auth readonly builtin role
484
- """
485
- client = self.app.test_client()
486
- token = self.login(client, USERNAME_READONLY, PASSWORD_READONLY)
487
- # Test unauthorized DELETE
488
- pk = 1
489
- uri = "api/v1/model1api/{}".format(pk)
490
- rv = self.auth_client_delete(client, token, uri)
491
- self.assertEqual(rv.status_code, 401)
492
-
493
- # Test unauthorized POST
494
- item = dict(
495
- field_string="test{}".format(MODEL1_DATA_SIZE + 1),
496
- field_integer=MODEL1_DATA_SIZE + 1,
497
- field_float=float(MODEL1_DATA_SIZE + 1),
498
- field_date=None,
499
- )
500
- uri = "api/v1/model1api/"
501
- rv = self.auth_client_post(client, token, uri, item)
502
- self.assertEqual(rv.status_code, 401)
503
-
504
- # Test authorized GET
505
- uri = "api/v1/model1api/1"
506
- rv = self.auth_client_get(client, token, uri)
507
- self.assertEqual(rv.status_code, 200)
508
-
509
- # Test authorized INFO
510
- uri = "api/v1/model1api/_info"
511
- rv = self.auth_client_get(client, token, uri)
512
- self.assertEqual(rv.status_code, 200)
513
-
514
- def test_base_rison_argument(self):
515
- """
516
- REST Api: Test not a valid rison argument
517
- """
518
- client = self.app.test_client()
519
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
520
- uri = "api/v1/model1api/?{}={}".format(API_URI_RIS_KEY, "(columns!(not_valid))")
521
- rv = self.auth_client_get(client, token, uri)
522
- self.assertEqual(rv.status_code, 400)
523
- data = json.loads(rv.data.decode("utf-8"))
524
- self.assertEqual(data, {"message": "Not a valid rison/json argument"})
525
- uri = "api/v1/model1api/1?{}={}".format(
526
- API_URI_RIS_KEY, "(columns!(not_valid))"
527
- )
528
- rv = self.auth_client_get(client, token, uri)
529
- self.assertEqual(rv.status_code, 400)
530
- data = json.loads(rv.data.decode("utf-8"))
531
- self.assertEqual(data, {"message": "Not a valid rison/json argument"})
532
-
533
- def test_base_rison_schema(self):
534
- """
535
- REST Api: Test rison schema validation
536
- """
537
- client = self.app.test_client()
538
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
539
- arguments = {"number": 1}
540
- uri = "api/v1/base1api/test1?{}={}".format(
541
- API_URI_RIS_KEY, prison.dumps(arguments)
542
- )
543
- rv = self.auth_client_get(client, token, uri)
544
- self.assertEqual(rv.status_code, 200)
545
- data = json.loads(rv.data.decode("utf-8"))
546
- self.assertEqual(data, {"message": "2"})
547
-
548
- # Rison Schema type validation
549
- arguments = {"number": "1"}
550
- uri = "api/v1/base1api/test1?{}={}".format(
551
- API_URI_RIS_KEY, prison.dumps(arguments)
552
- )
553
- rv = self.auth_client_get(client, token, uri)
554
- self.assertEqual(rv.status_code, 400)
555
-
556
- # Rison Schema validation required field
557
- arguments = {"numbers": 1}
558
- uri = "api/v1/base1api/test1?{}={}".format(
559
- API_URI_RIS_KEY, prison.dumps(arguments)
560
- )
561
- rv = self.auth_client_get(client, token, uri)
562
- self.assertEqual(rv.status_code, 400)
563
-
564
- def test_base_safe(self):
565
- """
566
- REST Api: Test safe decorator 500
567
- """
568
- client = self.app.test_client()
569
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
570
- uri = "api/v1/base1api/test2"
571
- rv = self.auth_client_get(client, token, uri)
572
- self.assertEqual(rv.status_code, 500)
573
- data = json.loads(rv.data.decode("utf-8"))
574
- self.assertEqual(data, {"message": "Fatal error"})
575
-
576
- def test_exclude_route_methods(self):
577
- """
578
- REST Api: Test exclude route methods
579
- """
580
- client = self.app.test_client()
581
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
582
- uri = "api/v1/model1apiexcluderoutes/_info"
583
- rv = self.auth_client_get(client, token, uri)
584
- self.assertEqual(rv.status_code, 405)
585
-
586
- uri = "api/v1/model1apiexcluderoutes/1"
587
- rv = self.auth_client_delete(client, token, uri)
588
- self.assertEqual(rv.status_code, 405)
589
-
590
- # Check that permissions do not exist
591
- pvm = self.appbuilder.sm.find_permission_view_menu(
592
- "can_info", "Model1ApiExcludeRoutes"
593
- )
594
- self.assertIsNone(pvm)
595
- pvm = self.appbuilder.sm.find_permission_view_menu(
596
- "can_delete", "Model1ApiExcludeRoutes"
597
- )
598
- self.assertIsNone(pvm)
599
-
600
- def test_include_route_methods(self):
601
- """
602
- REST Api: Test include route methods
603
- """
604
- client = self.app.test_client()
605
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
606
- uri = "api/v1/model1apiincluderoutes/_info"
607
- rv = self.auth_client_get(client, token, uri)
608
- self.assertEqual(rv.status_code, 404)
609
-
610
- uri = "api/v1/model1apiincluderoutes/1"
611
- rv = self.auth_client_get(client, token, uri)
612
- self.assertEqual(rv.status_code, 200)
613
-
614
- # Check that permissions do not exist
615
- pvm = self.appbuilder.sm.find_permission_view_menu(
616
- "can_info", "Model1ApiIncludeRoutes"
617
- )
618
- self.assertIsNone(pvm)
619
- pvm = self.appbuilder.sm.find_permission_view_menu(
620
- "can_put", "Model1ApiIncludeRoutes"
621
- )
622
- self.assertIsNone(pvm)
623
- pvm = self.appbuilder.sm.find_permission_view_menu(
624
- "can_post", "Model1ApiIncludeRoutes"
625
- )
626
- self.assertIsNone(pvm)
627
- pvm = self.appbuilder.sm.find_permission_view_menu(
628
- "can_delete", "Model1ApiIncludeRoutes"
629
- )
630
- self.assertIsNone(pvm)
631
-
632
- def test_get_item(self):
633
- """
634
- REST Api: Test get item
635
- """
636
- client = self.app.test_client()
637
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
638
- for i in range(1, MODEL1_DATA_SIZE):
639
- rv = self.auth_client_get(client, token, "api/v1/model1api/{}".format(i))
640
- data = json.loads(rv.data.decode("utf-8"))
641
- self.assertEqual(rv.status_code, 200)
642
- self.assert_get_item(rv, data, i - 1)
643
-
644
- def assert_get_item(self, rv, data, value):
645
- self.assertEqual(
646
- data[API_RESULT_RES_KEY],
647
- {
648
- "field_date": None,
649
- "field_float": float(value),
650
- "field_integer": value,
651
- "field_string": "test{}".format(value),
652
- },
653
- )
654
- # test descriptions
655
- self.assertEqual(
656
- data["description_columns"], self.model1api.description_columns
657
- )
658
- # test labels
659
- self.assertEqual(
660
- data[API_LABEL_COLUMNS_RES_KEY],
661
- {
662
- "field_date": "Field Date",
663
- "field_float": "Field Float",
664
- "field_integer": "Field Integer",
665
- "field_string": "Field String",
666
- },
667
- )
668
- self.assertEqual(rv.status_code, 200)
669
-
670
- def test_get_item_select_cols(self):
671
- """
672
- REST Api: Test get item with select columns
673
- """
674
- client = self.app.test_client()
675
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
676
-
677
- for i in range(1, MODEL1_DATA_SIZE):
678
- uri = "api/v1/model1api/{}?q=({}:!(field_integer))".format(
679
- i, API_SELECT_COLUMNS_RIS_KEY
680
- )
681
- rv = self.auth_client_get(client, token, uri)
682
- data = json.loads(rv.data.decode("utf-8"))
683
- self.assertEqual(data[API_RESULT_RES_KEY], {"field_integer": i - 1})
684
- self.assertEqual(
685
- data[API_DESCRIPTION_COLUMNS_RES_KEY],
686
- {"field_integer": "Field Integer"},
687
- )
688
- self.assertEqual(
689
- data[API_LABEL_COLUMNS_RES_KEY], {"field_integer": "Field Integer"}
690
- )
691
- self.assertEqual(rv.status_code, 200)
692
-
693
- def test_get_item_dotted_mo_notation(self):
694
- """
695
- REST Api: Test get item with dotted M-O related field
696
- """
697
- client = self.app.test_client()
698
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
699
- model2 = (
700
- self.appbuilder.get_session.query(Model2)
701
- .filter_by(field_string="test0")
702
- .one_or_none()
703
- )
704
- pk = model2.id
705
- uri = f"api/v1/model2dottednotationapi/{pk}"
706
- rv = self.auth_client_get(client, token, uri)
707
- data = json.loads(rv.data.decode("utf-8"))
708
- self.assertEqual(
709
- data[API_RESULT_RES_KEY],
710
- {"field_string": "test0", "group": {"field_string": "test0"}},
711
- )
712
- self.assertEqual(rv.status_code, 200)
713
-
714
- def test_get_item_select_meta_data(self):
715
- """
716
- REST Api: Test get item select meta data
717
- """
718
- client = self.app.test_client()
719
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
720
-
721
- selectable_keys = [
722
- API_DESCRIPTION_COLUMNS_RIS_KEY,
723
- API_LABEL_COLUMNS_RIS_KEY,
724
- API_SHOW_COLUMNS_RIS_KEY,
725
- API_SHOW_TITLE_RIS_KEY,
726
- ]
727
- for selectable_key in selectable_keys:
728
- argument = {API_SELECT_KEYS_RIS_KEY: [selectable_key]}
729
- uri = "api/v1/model1api/1?{}={}".format(
730
- API_URI_RIS_KEY, prison.dumps(argument)
731
- )
732
- rv = self.auth_client_get(client, token, uri)
733
- data = json.loads(rv.data.decode("utf-8"))
734
- self.assertEqual(len(data.keys()), 1 + 2) # always exist id, result
735
- # We assume that rison meta key equals result meta key
736
- assert selectable_key in data
737
-
738
- def test_get_item_excluded_cols(self):
739
- """
740
- REST Api: Test get item with excluded columns
741
- """
742
- client = self.app.test_client()
743
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
744
-
745
- pk = 1
746
- rv = self.auth_client_get(
747
- client, token, "api/v1/model1apiexcludecols/{}".format(pk)
748
- )
749
- data = json.loads(rv.data.decode("utf-8"))
750
- self.assertEqual(data[API_RESULT_RES_KEY], {"field_string": "test0"})
751
- self.assertEqual(rv.status_code, 200)
752
-
753
- def test_get_item_not_found(self):
754
- """
755
- REST Api: Test get item not found
756
- """
757
- client = self.app.test_client()
758
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
759
-
760
- pk = MODEL1_DATA_SIZE + 1
761
- rv = self.auth_client_get(client, token, "api/v1/model1api/{}".format(pk))
762
- self.assertEqual(rv.status_code, 404)
763
-
764
- def test_get_item_base_filters(self):
765
- """
766
- REST Api: Test get item with base filters
767
- """
768
- client = self.app.test_client()
769
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
770
-
771
- # We can't get a base filtered item
772
- pk = 1
773
- rv = self.auth_client_get(
774
- client, token, "api/v1/model1apifiltered/{}".format(pk)
775
- )
776
- self.assertEqual(rv.status_code, 404)
777
- # This one is ok pk=4 field_integer=3 2>3<4
778
- pk = 4
779
- rv = self.auth_client_get(client, token, f"api/v1/model1apifiltered/{pk}")
780
- self.assertEqual(rv.status_code, 200)
781
-
782
- def test_get_item_mo_field(self):
783
- """
784
- REST Api: Test get item with M-O related field
785
- """
786
- client = self.app.test_client()
787
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
788
-
789
- # We can't get a base filtered item
790
- model2 = (
791
- self.appbuilder.get_session.query(Model2)
792
- .filter_by(field_string="test0")
793
- .one_or_none()
794
- )
795
- pk = model2.id
796
- rv = self.auth_client_get(client, token, f"api/v1/model2api/{pk}")
797
- data = json.loads(rv.data.decode("utf-8"))
798
- self.assertEqual(rv.status_code, 200)
799
- expected_rel_field = {
800
- "group": {
801
- "field_date": None,
802
- "field_float": 0.0,
803
- "field_integer": 0,
804
- "field_string": "test0",
805
- "id": 1,
806
- }
807
- }
808
- self.assertEqual(data[API_RESULT_RES_KEY], expected_rel_field)
809
-
810
- def test_get_item_mm_field(self):
811
- """
812
- REST Api: Test get item with M-M related field
813
- """
814
- client = self.app.test_client()
815
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
816
-
817
- # We can't get a base filtered item
818
- pk = 1
819
- rv = self.auth_client_get(client, token, f"api/v1/modelmmapi/{pk}")
820
- data = json.loads(rv.data.decode("utf-8"))
821
- self.assertEqual(rv.status_code, 200)
822
- expected_rel_field = [
823
- {"field_string": "1", "field_integer": 1, "id": 1},
824
- {"field_string": "2", "field_integer": 2, "id": 2},
825
- {"field_string": "3", "field_integer": 3, "id": 3},
826
- ]
827
- self.assertEqual(data[API_RESULT_RES_KEY]["children"], expected_rel_field)
828
-
829
- def test_get_item_dotted_mm_field(self):
830
- """
831
- REST Api: Test get item with dotted M-M related field
832
- """
833
- client = self.app.test_client()
834
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
835
-
836
- # We can't get a base filtered item
837
- pk = 1
838
- rv = self.auth_client_get(client, token, f"api/v1/modeldottedmmapi/{pk}")
839
- data = json.loads(rv.data.decode("utf-8"))
840
- self.assertEqual(rv.status_code, 200)
841
- expected_result = {
842
- "field_string": "0",
843
- "children": [
844
- {"field_integer": 1},
845
- {"field_integer": 2},
846
- {"field_integer": 3},
847
- ],
848
- }
849
- self.assertEqual(data[API_RESULT_RES_KEY], expected_result)
850
-
851
- def test_get_item_om_field(self):
852
- """
853
- REST Api: Test get item with O-M related field
854
- """
855
- client = self.app.test_client()
856
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
857
-
858
- # We can't get a base filtered item
859
- pk = 1
860
- rv = self.auth_client_get(
861
- client, token, "api/v1/modelomparentapi/{}".format(pk)
862
- )
863
- data = json.loads(rv.data.decode("utf-8"))
864
- self.assertEqual(rv.status_code, 200)
865
- expected_rel_field = [
866
- {"field_string": f"text0.{i}", "id": i}
867
- for i in range(1, MODELOMCHILD_DATA_SIZE)
868
- ]
869
- self.assertEqual(data[API_RESULT_RES_KEY]["children"], expected_rel_field)
870
-
871
- def test_get_list(self):
872
- """
873
- REST Api: Test get list
874
- """
875
- client = self.app.test_client()
876
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
877
-
878
- rv = self.auth_client_get(client, token, "api/v1/model1api/")
879
-
880
- data = json.loads(rv.data.decode("utf-8"))
881
- # Tests count property
882
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
883
- # Tests data result default page size
884
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
885
-
886
- def test_get_list_dotted_mo_field(self):
887
- """
888
- REST Api: Test get list with dotted M-O related field
889
- """
890
- client = self.app.test_client()
891
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
892
-
893
- arguments = {"order_column": "field_string", "order_direction": "asc"}
894
- uri = "api/v1/model2dottednotationapi/?{}={}".format(
895
- API_URI_RIS_KEY, prison.dumps(arguments)
896
- )
897
- rv = self.auth_client_get(client, token, uri)
898
- data = json.loads(rv.data.decode("utf-8"))
899
- # Tests count property
900
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
901
- # Tests data result default page size
902
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
903
- i = 0
904
- self.assertEqual(
905
- data[API_RESULT_RES_KEY][i],
906
- {"field_string": "test0", "group": {"field_string": "test0"}},
907
- )
908
-
909
- def test_get_list_om_field(self):
910
- """
911
- REST Api: Test get list with O-M related field
912
- """
913
- client = self.app.test_client()
914
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
915
-
916
- rv = self.auth_client_get(client, token, "api/v1/modelomparentapi/")
917
- data = json.loads(rv.data.decode("utf-8"))
918
- self.assertEqual(rv.status_code, 200)
919
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
920
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
921
- expected_rel_field = [
922
- {"field_string": f"text0.{i}", "id": i}
923
- for i in range(1, MODELOMCHILD_DATA_SIZE)
924
- ]
925
- self.assertEqual(data[API_RESULT_RES_KEY][0]["children"], expected_rel_field)
926
-
927
- def test_get_list_dotted_om_field(self):
928
- """
929
- REST Api: Test get list with dotted O-M related field
930
- """
931
- client = self.app.test_client()
932
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
933
-
934
- rv = self.auth_client_get(client, token, "api/v1/modeldottedomparentapi/")
935
- data = json.loads(rv.data.decode("utf-8"))
936
- self.assertEqual(rv.status_code, 200)
937
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
938
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
939
- expected_rel_field = [
940
- {"field_string": f"text0.{i}"} for i in range(1, MODELOMCHILD_DATA_SIZE)
941
- ]
942
- self.assertEqual(data[API_RESULT_RES_KEY][0]["children"], expected_rel_field)
943
-
944
- def test_get_list_dotted_mm_field(self):
945
- """
946
- REST Api: Test get list with dotted M-M related field
947
- """
948
- client = self.app.test_client()
949
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
950
-
951
- arguments = {"order_column": "field_string", "order_direction": "asc"}
952
- uri = (
953
- f"api/v1/modeldottedmmapi/?" f"{API_URI_RIS_KEY}={prison.dumps(arguments)}"
954
- )
955
- rv = self.auth_client_get(client, token, uri)
956
- data = json.loads(rv.data.decode("utf-8"))
957
- self.assertEqual(rv.status_code, 200)
958
- self.assertEqual(data["count"], MODEL2_DATA_SIZE)
959
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.modeldottedmmapi.page_size)
960
- i = 0
961
- self.assertEqual(data[API_RESULT_RES_KEY][i]["field_string"], "0")
962
- self.assertEqual(len(data[API_RESULT_RES_KEY][i]["children"]), 3)
963
- self.assertIn({"field_integer": 1}, data[API_RESULT_RES_KEY][i]["children"])
964
- self.assertIn({"field_integer": 2}, data[API_RESULT_RES_KEY][i]["children"])
965
- self.assertIn({"field_integer": 3}, data[API_RESULT_RES_KEY][i]["children"])
966
-
967
- def test_get_list_dotted_mo_order(self):
968
- """
969
- REST Api: Test get list and order dotted M-O notation
970
- """
971
- client = self.app.test_client()
972
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
973
-
974
- arguments = {"order_column": "group.field_string", "order_direction": "desc"}
975
- uri = "api/v1/model2dottednotationapi/?{}={}".format(
976
- API_URI_RIS_KEY, prison.dumps(arguments)
977
- )
978
- rv = self.auth_client_get(client, token, uri)
979
- data = json.loads(rv.data.decode("utf-8"))
980
- # Tests count property
981
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
982
- # Tests data result default page size
983
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
984
- i = 0
985
- self.assertEqual(
986
- data[API_RESULT_RES_KEY][i],
987
- {"field_string": "test9", "group": {"field_string": "test9"}},
988
- )
989
-
990
- def test_get_list_multiple_dotted_order(self):
991
- """
992
- REST Api: Test get list order multiple dotted notation
993
- """
994
-
995
- class Model4Api(ModelRestApi):
996
- datamodel = SQLAInterface(Model4)
997
- list_columns = [
998
- "field_string",
999
- "model1_1.field_string",
1000
- "model1_2.field_string",
1001
- ]
1002
-
1003
- self.appbuilder.add_api(Model4Api)
1004
-
1005
- client = self.app.test_client()
1006
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1007
-
1008
- # Test order asc for model1_1
1009
- arguments = {"order_column": "model1_1.field_string", "order_direction": "desc"}
1010
- uri = "api/v1/model4api/?{}={}".format(API_URI_RIS_KEY, prison.dumps(arguments))
1011
- rv = self.auth_client_get(client, token, uri)
1012
- data = json.loads(rv.data.decode("utf-8"))
1013
- # Tests count property
1014
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
1015
- # Tests data result default page size
1016
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
1017
- i = 0
1018
- self.assertEqual(
1019
- data[API_RESULT_RES_KEY][i],
1020
- {
1021
- "field_string": "test9",
1022
- "model1_1": {"field_string": "test9"},
1023
- "model1_2": {"field_string": "test9"},
1024
- },
1025
- )
1026
-
1027
- # Test order desc for model1_2
1028
- arguments = {"order_column": "model1_2.field_string", "order_direction": "asc"}
1029
- uri = "api/v1/model4api/?{}={}".format(API_URI_RIS_KEY, prison.dumps(arguments))
1030
- rv = self.auth_client_get(client, token, uri)
1031
- data = json.loads(rv.data.decode("utf-8"))
1032
- # Tests count property
1033
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
1034
- # Tests data result default page size
1035
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
1036
- i = 0
1037
- self.assertEqual(
1038
- data[API_RESULT_RES_KEY][i],
1039
- {
1040
- "field_string": "test0",
1041
- "model1_1": {"field_string": "test0"},
1042
- "model1_2": {"field_string": "test0"},
1043
- },
1044
- )
1045
-
1046
- def test_get_list_order(self):
1047
- """
1048
- REST Api: Test get list order params
1049
- """
1050
- client = self.app.test_client()
1051
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1052
-
1053
- # test string order asc
1054
- arguments = {"order_column": "field_integer", "order_direction": "asc"}
1055
- uri = "api/v1/model1api/?{}={}".format(API_URI_RIS_KEY, prison.dumps(arguments))
1056
- rv = self.auth_client_get(client, token, uri)
1057
- data = json.loads(rv.data.decode("utf-8"))
1058
- self.assertEqual(
1059
- data[API_RESULT_RES_KEY][0],
1060
- {
1061
- "field_date": None,
1062
- "field_float": 0.0,
1063
- "field_integer": 0,
1064
- "field_string": "test0",
1065
- },
1066
- )
1067
- self.assertEqual(rv.status_code, 200)
1068
- # test string order desc
1069
- arguments = {"order_column": "field_integer", "order_direction": "desc"}
1070
- uri = "api/v1/model1api/?{}={}".format(API_URI_RIS_KEY, prison.dumps(arguments))
1071
- rv = self.auth_client_get(client, token, uri)
1072
- data = json.loads(rv.data.decode("utf-8"))
1073
- self.assertEqual(
1074
- data[API_RESULT_RES_KEY][0],
1075
- {
1076
- "field_date": None,
1077
- "field_float": float(MODEL1_DATA_SIZE - 1),
1078
- "field_integer": MODEL1_DATA_SIZE - 1,
1079
- "field_string": "test{}".format(MODEL1_DATA_SIZE - 1),
1080
- },
1081
- )
1082
- self.assertEqual(rv.status_code, 200)
1083
-
1084
- def test_get_list_base_order(self):
1085
- """
1086
- REST Api: Test get list with base order
1087
- """
1088
- client = self.app.test_client()
1089
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1090
-
1091
- # test string order asc
1092
- rv = self.auth_client_get(client, token, "api/v1/model1apiorder/")
1093
- data = json.loads(rv.data.decode("utf-8"))
1094
- self.assertEqual(
1095
- data[API_RESULT_RES_KEY][0],
1096
- {
1097
- "field_date": None,
1098
- "field_float": float(MODEL1_DATA_SIZE - 1),
1099
- "field_integer": MODEL1_DATA_SIZE - 1,
1100
- "field_string": "test{}".format(MODEL1_DATA_SIZE - 1),
1101
- },
1102
- )
1103
- # Test override
1104
- arguments = {"order_column": "field_integer", "order_direction": "asc"}
1105
- uri = f"api/v1/model1apiorder/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1106
- rv = self.auth_client_get(client, token, uri)
1107
- data = json.loads(rv.data.decode("utf-8"))
1108
- self.assertEqual(
1109
- data[API_RESULT_RES_KEY][0],
1110
- {
1111
- "field_date": None,
1112
- "field_float": 0.0,
1113
- "field_integer": 0,
1114
- "field_string": "test0",
1115
- },
1116
- )
1117
-
1118
- def test_get_list_page(self):
1119
- """
1120
- REST Api: Test get list page params
1121
- """
1122
- page_size = 5
1123
- client = self.app.test_client()
1124
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1125
-
1126
- # test page zero
1127
- arguments = {
1128
- "page_size": page_size,
1129
- "page": 0,
1130
- "order_column": "field_integer",
1131
- "order_direction": "asc",
1132
- }
1133
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1134
- rv = self.auth_client_get(client, token, uri)
1135
- data = json.loads(rv.data.decode("utf-8"))
1136
- self.assertEqual(
1137
- data[API_RESULT_RES_KEY][0],
1138
- {
1139
- "field_date": None,
1140
- "field_float": 0.0,
1141
- "field_integer": 0,
1142
- "field_string": "test0",
1143
- },
1144
- )
1145
- self.assertEqual(rv.status_code, 200)
1146
- self.assertEqual(len(data[API_RESULT_RES_KEY]), page_size)
1147
- # test page one
1148
- arguments = {
1149
- "page_size": page_size,
1150
- "page": 1,
1151
- "order_column": "field_integer",
1152
- "order_direction": "asc",
1153
- }
1154
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1155
- rv = self.auth_client_get(client, token, uri)
1156
-
1157
- data = json.loads(rv.data.decode("utf-8"))
1158
- self.assertEqual(
1159
- data[API_RESULT_RES_KEY][0],
1160
- {
1161
- "field_date": None,
1162
- "field_float": float(page_size),
1163
- "field_integer": page_size,
1164
- "field_string": "test{}".format(page_size),
1165
- },
1166
- )
1167
- self.assertEqual(rv.status_code, 200)
1168
- self.assertEqual(len(data[API_RESULT_RES_KEY]), page_size)
1169
-
1170
- # test simple page test, mainly because of MSSQL dialect
1171
- arguments = {"page_size": page_size, "page": 1}
1172
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1173
- rv = self.auth_client_get(client, token, uri)
1174
- self.assertEquals(rv.status_code, 200)
1175
-
1176
- def test_get_list_max_page_size(self):
1177
- """
1178
- REST Api: Test get list max page size config setting
1179
- """
1180
- page_size = 200 # Max is globally set to MAX_PAGE_SIZE
1181
- client = self.app.test_client()
1182
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1183
-
1184
- # test page zero
1185
- arguments = {
1186
- "page_size": page_size,
1187
- "page": 0,
1188
- "order_column": "field_integer",
1189
- "order_direction": "asc",
1190
- }
1191
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1192
- rv = self.auth_client_get(client, token, uri)
1193
- data = json.loads(rv.data.decode("utf-8"))
1194
- self.assertEqual(len(data[API_RESULT_RES_KEY]), MAX_PAGE_SIZE)
1195
-
1196
- def test_get_list_max_page_size_override(self):
1197
- """
1198
- REST Api: Test get list max page size property override
1199
- """
1200
-
1201
- class Model1PageSizeOverride(ModelRestApi):
1202
- datamodel = SQLAInterface(Model1)
1203
- max_page_size = -1
1204
- list_columns = [
1205
- "field_integer",
1206
- "field_float",
1207
- "field_string",
1208
- "field_date",
1209
- ]
1210
- description_columns = {
1211
- "field_integer": "Field Integer",
1212
- "field_float": "Field Float",
1213
- "field_string": "Field String",
1214
- }
1215
-
1216
- self.model1api = Model1PageSizeOverride
1217
- self.appbuilder.add_api(Model1PageSizeOverride)
1218
-
1219
- page_size = 200 # Max is globally set to MAX_PAGE_SIZE
1220
- client = self.app.test_client()
1221
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1222
-
1223
- # test page zero
1224
- arguments = {
1225
- "page_size": page_size,
1226
- "page": 0,
1227
- "order_column": "field_integer",
1228
- "order_direction": "asc",
1229
- }
1230
- endpoint = "api/v1/model1pagesizeoverride/"
1231
- uri = f"{endpoint}?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1232
- rv = self.auth_client_get(client, token, uri)
1233
- data = json.loads(rv.data.decode("utf-8"))
1234
- self.assertEqual(len(data[API_RESULT_RES_KEY]), MODEL1_DATA_SIZE)
1235
-
1236
- def test_get_list_filters(self):
1237
- """
1238
- REST Api: Test get list filter params
1239
- """
1240
- client = self.app.test_client()
1241
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1242
-
1243
- filter_value = 5
1244
- # test string order asc
1245
- arguments = {
1246
- API_FILTERS_RIS_KEY: [
1247
- {"col": "field_integer", "opr": "gt", "value": filter_value}
1248
- ],
1249
- "order_column": "field_integer",
1250
- "order_direction": "asc",
1251
- }
1252
-
1253
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1254
- expected_result = {
1255
- "field_date": None,
1256
- "field_float": float(filter_value + 1),
1257
- "field_integer": filter_value + 1,
1258
- "field_string": "test{}".format(filter_value + 1),
1259
- }
1260
- rv = self.auth_client_get(client, token, uri)
1261
- data = json.loads(rv.data.decode("utf-8"))
1262
- self.assertEqual(data[API_RESULT_RES_KEY][0], expected_result)
1263
- self.assertEqual(rv.status_code, 200)
1264
-
1265
- # Test with JSON encode content
1266
- from urllib.parse import quote
1267
-
1268
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={quote(json.dumps(arguments))}"
1269
- rv = self.auth_client_get(client, token, uri)
1270
- data = json.loads(rv.data.decode("utf-8"))
1271
- self.assertEqual(data[API_RESULT_RES_KEY][0], expected_result)
1272
- self.assertEqual(rv.status_code, 200)
1273
-
1274
- def test_get_list_invalid_filters(self):
1275
- """
1276
- REST Api: Test get list filter params
1277
- """
1278
- client = self.app.test_client()
1279
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1280
-
1281
- arguments = {API_FILTERS_RIS_KEY: [{"col": "field_integer", "opr": "gt"}]}
1282
-
1283
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1284
- rv = self.auth_client_get(client, token, uri)
1285
- self.assertEqual(rv.status_code, 400)
1286
-
1287
- def test_get_list_filters_m_m(self):
1288
- """
1289
- REST Api: Test get list filter params with many to many
1290
- """
1291
- session = self.appbuilder.get_session
1292
-
1293
- child = ModelMMChild()
1294
- child.field_string = "test_child_tmp"
1295
- children = [child]
1296
- session.add(child)
1297
- session.commit()
1298
- parent = ModelMMParent()
1299
- parent.field_string = "test_tmp"
1300
- parent.children = children
1301
- session.add(parent)
1302
- session.commit()
1303
-
1304
- client = self.app.test_client()
1305
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1306
- arguments = {
1307
- API_FILTERS_RIS_KEY: [{"col": "children", "opr": "rel_m_m", "value": [4]}]
1308
- }
1309
-
1310
- uri = f"api/v1/modelmmapi/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1311
- rv = self.auth_client_get(client, token, uri)
1312
- data = json.loads(rv.data.decode("utf-8"))
1313
- self.assertEqual(data["count"], 1)
1314
- self.assertEqual(
1315
- data["result"][0]["children"][0]["field_string"], "test_child_tmp"
1316
- )
1317
-
1318
- arguments = {
1319
- API_FILTERS_RIS_KEY: [
1320
- {"col": "children", "opr": "rel_m_m", "value": [1, 2]}
1321
- ]
1322
- }
1323
-
1324
- uri = f"api/v1/modelmmapi/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1325
- rv = self.auth_client_get(client, token, uri)
1326
- data = json.loads(rv.data.decode("utf-8"))
1327
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
1328
-
1329
- parent_ = (
1330
- session.query(ModelMMParent)
1331
- .filter_by(field_string="test_tmp")
1332
- .one_or_none()
1333
- )
1334
- child_ = (
1335
- session.query(ModelMMChild)
1336
- .filter_by(field_string="test_child_tmp")
1337
- .one_or_none()
1338
- )
1339
-
1340
- session.delete(parent_)
1341
- session.commit()
1342
- session.delete(child_)
1343
- session.commit()
1344
-
1345
- def test_get_list_filters_wrong_col(self):
1346
- """
1347
- REST Api: Test get list with wrong columns
1348
- """
1349
- client = self.app.test_client()
1350
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1351
-
1352
- filter_value = "value"
1353
- arguments = {
1354
- API_FILTERS_RIS_KEY: [
1355
- {"col": "wrong_columns", "opr": "sw", "value": filter_value},
1356
- {"col": "field_string", "opr": "sw", "value": filter_value},
1357
- ]
1358
- }
1359
-
1360
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1361
-
1362
- rv = self.auth_client_get(client, token, uri)
1363
- self.assertEqual(rv.status_code, 400)
1364
-
1365
- def test_get_list_filters_wrong_opr(self):
1366
- """
1367
- REST Api: Test get list with wrong operation
1368
- """
1369
- client = self.app.test_client()
1370
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1371
-
1372
- filter_value = 1
1373
- arguments = {
1374
- API_FILTERS_RIS_KEY: [
1375
- {"col": "field_integer", "opr": "sw", "value": filter_value}
1376
- ]
1377
- }
1378
-
1379
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1380
-
1381
- rv = self.auth_client_get(client, token, uri)
1382
- self.assertEqual(rv.status_code, 400)
1383
-
1384
- def test_get_list_filters_wrong_order(self):
1385
- """
1386
- REST Api: Test get list with wrong order column
1387
- """
1388
- client = self.app.test_client()
1389
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1390
-
1391
- arguments = {"order_column": "wrong_column", "order_direction": "asc"}
1392
-
1393
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1394
-
1395
- rv = self.auth_client_get(client, token, uri)
1396
- self.assertEqual(rv.status_code, 400)
1397
-
1398
- def test_get_list_multiple_search_filters(self):
1399
- """
1400
- REST Api: Test get list multiple search filters
1401
- """
1402
- session = self.appbuilder.get_session
1403
- model1_1 = Model1(field_string="abc", field_integer=6)
1404
- session.add(model1_1)
1405
- session.commit()
1406
-
1407
- arguments = {
1408
- API_FILTERS_RIS_KEY: [
1409
- {"col": "field_integer", "opr": "gt", "value": 5},
1410
- {"col": "field_integer", "opr": "lt", "value": 7},
1411
- ]
1412
- }
1413
- rison_args = prison.dumps(arguments)
1414
- uri = f"api/v1/model1apisearchfilters/?{API_URI_RIS_KEY}={rison_args}"
1415
-
1416
- client = self.app.test_client()
1417
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1418
- rv = self.auth_client_get(client, token, uri)
1419
- self.assertEqual(rv.status_code, 200)
1420
- data = json.loads(rv.data.decode("utf-8"))
1421
- self.assertEqual(data["count"], 2)
1422
-
1423
- arguments = {
1424
- API_FILTERS_RIS_KEY: [
1425
- {"col": "field_integer", "opr": "gt", "value": 5},
1426
- {"col": "field_integer", "opr": "lt", "value": 7},
1427
- {"col": "field_string", "opr": "sw", "value": "a"},
1428
- ]
1429
- }
1430
- rison_args = prison.dumps(arguments)
1431
- uri = f"api/v1/model1apisearchfilters/?{API_URI_RIS_KEY}={rison_args}"
1432
-
1433
- rv = self.auth_client_get(client, token, uri)
1434
- self.assertEqual(rv.status_code, 200)
1435
- data = json.loads(rv.data.decode("utf-8"))
1436
- self.assertEqual(data["count"], 1)
1437
- self.assertEqual(data["result"][0]["field_string"], "abc")
1438
-
1439
- session.delete(model1_1)
1440
- session.commit()
1441
-
1442
- def test_get_list_custom_search_filters(self):
1443
- """
1444
- REST Api: Test get list custom filters
1445
- """
1446
- session = self.appbuilder.get_session
1447
- model1_1 = Model1(field_string="abc", field_integer=2)
1448
- # Custom filter will get this next model (not like 'test' and field_integer=1)
1449
- model1_2 = Model1(field_string="abcd", field_integer=1)
1450
- session.add(model1_1)
1451
- session.add(model1_2)
1452
- session.commit()
1453
-
1454
- filter_value = "test"
1455
- arguments = {
1456
- API_FILTERS_RIS_KEY: [
1457
- {"col": "field_string", "opr": "custom_filter", "value": filter_value}
1458
- ]
1459
- }
1460
- rison_args = prison.dumps(arguments)
1461
- uri = f"api/v1/model1apisearchfilters/?{API_URI_RIS_KEY}={rison_args}"
1462
-
1463
- client = self.app.test_client()
1464
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1465
- rv = self.auth_client_get(client, token, uri)
1466
- self.assertEqual(rv.status_code, 200)
1467
- data = json.loads(rv.data.decode("utf-8"))
1468
- self.assertEqual(data["count"], 1)
1469
- expected_result = [
1470
- {
1471
- "field_date": None,
1472
- "field_float": None,
1473
- "field_integer": 1,
1474
- "field_string": "abcd",
1475
- }
1476
- ]
1477
- self.assertEqual(data[API_RESULT_RES_KEY], expected_result)
1478
-
1479
- arguments = {
1480
- API_FILTERS_RIS_KEY: [
1481
- {"col": "field_string", "opr": "custom_filter", "value": filter_value},
1482
- {"col": "field_integer", "opr": "eq", "value": 3},
1483
- ]
1484
- }
1485
- rison_args = prison.dumps(arguments)
1486
- uri = f"api/v1/model1apisearchfilters/?{API_URI_RIS_KEY}={rison_args}"
1487
- rv = self.auth_client_get(client, token, uri)
1488
- self.assertEqual(rv.status_code, 200)
1489
- data = json.loads(rv.data.decode("utf-8"))
1490
- self.assertEqual(data["count"], 0)
1491
- session.delete(model1_1)
1492
- session.delete(model1_2)
1493
- session.commit()
1494
-
1495
- def test_get_info_custom_search_filters(self):
1496
- """
1497
- REST Api: Test get info custom filters
1498
- """
1499
- arguments = {"keys": ["filters"]}
1500
- rison_args = prison.dumps(arguments)
1501
- uri = f"api/v1/model1apisearchfilters/_info?{API_URI_RIS_KEY}={rison_args}"
1502
-
1503
- client = self.app.test_client()
1504
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1505
- rv = self.auth_client_get(client, token, uri)
1506
- self.assertEqual(rv.status_code, 200)
1507
- data = json.loads(rv.data.decode("utf-8"))
1508
- field_string_filters = data["filters"]["field_string"]
1509
- self.assertIn(
1510
- {"name": "Custom Filter", "operator": "custom_filter"}, field_string_filters
1511
- )
1512
-
1513
- def test_get_list_select_cols(self):
1514
- """
1515
- REST Api: Test get list with select columns
1516
- """
1517
- client = self.app.test_client()
1518
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1519
-
1520
- argument = {
1521
- API_SELECT_COLUMNS_RIS_KEY: ["field_integer"],
1522
- "order_column": "field_integer",
1523
- "order_direction": "asc",
1524
- }
1525
-
1526
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(argument)}"
1527
- rv = self.auth_client_get(client, token, uri)
1528
- data = json.loads(rv.data.decode("utf-8"))
1529
- self.assertEqual(data[API_RESULT_RES_KEY][0], {"field_integer": 0})
1530
- self.assertEqual(
1531
- data[API_LABEL_COLUMNS_RES_KEY], {"field_integer": "Field Integer"}
1532
- )
1533
- self.assertEqual(
1534
- data[API_DESCRIPTION_COLUMNS_RES_KEY], {"field_integer": "Field Integer"}
1535
- )
1536
- self.assertEqual(data[API_LIST_COLUMNS_RES_KEY], ["field_integer"])
1537
- self.assertEqual(rv.status_code, 200)
1538
-
1539
- def test_get_list_select_meta_data(self):
1540
- """
1541
- REST Api: Test get list select meta data
1542
- """
1543
- client = self.app.test_client()
1544
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1545
-
1546
- selectable_keys = [
1547
- API_DESCRIPTION_COLUMNS_RIS_KEY,
1548
- API_LABEL_COLUMNS_RIS_KEY,
1549
- API_ORDER_COLUMNS_RIS_KEY,
1550
- API_LIST_COLUMNS_RIS_KEY,
1551
- API_LIST_TITLE_RIS_KEY,
1552
- ]
1553
- for selectable_key in selectable_keys:
1554
- argument = {API_SELECT_KEYS_RIS_KEY: [selectable_key]}
1555
- uri = f"api/v1/model1api/?{API_URI_RIS_KEY}={prison.dumps(argument)}"
1556
- rv = self.auth_client_get(client, token, uri)
1557
- data = json.loads(rv.data.decode("utf-8"))
1558
- self.assertEqual(len(data.keys()), 1 + 3) # always exist count, ids, result
1559
- # We assume that rison meta key equals result meta key
1560
- assert selectable_key in data
1561
-
1562
- def test_get_list_exclude_cols(self):
1563
- """
1564
- REST Api: Test get list with excluded columns
1565
- """
1566
- client = self.app.test_client()
1567
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1568
-
1569
- uri = "api/v1/model1apiexcludecols/"
1570
- rv = self.auth_client_get(client, token, uri)
1571
- data = json.loads(rv.data.decode("utf-8"))
1572
- self.assertEqual(data[API_RESULT_RES_KEY][0], {"field_string": "test0"})
1573
-
1574
- def test_get_list_base_filters(self):
1575
- """
1576
- REST Api: Test get list with base filters
1577
- """
1578
- client = self.app.test_client()
1579
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1580
-
1581
- arguments = {"order_column": "field_integer", "order_direction": "desc"}
1582
- uri = f"api/v1/model1apifiltered/?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1583
- rv = self.auth_client_get(client, token, uri)
1584
- data = json.loads(rv.data.decode("utf-8"))
1585
- expected_result = [
1586
- {
1587
- "field_date": None,
1588
- "field_float": 3.0,
1589
- "field_integer": 3,
1590
- "field_string": "test3",
1591
- }
1592
- ]
1593
- self.assertEqual(data[API_RESULT_RES_KEY], expected_result)
1594
-
1595
- def test_info_filters(self):
1596
- """
1597
- REST Api: Test info filters
1598
- """
1599
- client = self.app.test_client()
1600
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1601
- uri = "api/v1/model1api/_info"
1602
- rv = self.auth_client_get(client, token, uri)
1603
- data = json.loads(rv.data.decode("utf-8"))
1604
- expected_filters = {
1605
- "field_date": [
1606
- {"name": "Equal to", "operator": "eq"},
1607
- {"name": "Greater than", "operator": "gt"},
1608
- {"name": "Smaller than", "operator": "lt"},
1609
- {"name": "Not Equal to", "operator": "neq"},
1610
- ],
1611
- "field_float": [
1612
- {"name": "Equal to", "operator": "eq"},
1613
- {"name": "Greater than", "operator": "gt"},
1614
- {"name": "Smaller than", "operator": "lt"},
1615
- {"name": "Not Equal to", "operator": "neq"},
1616
- ],
1617
- "field_integer": [
1618
- {"name": "Equal to", "operator": "eq"},
1619
- {"name": "Greater than", "operator": "gt"},
1620
- {"name": "Smaller than", "operator": "lt"},
1621
- {"name": "Not Equal to", "operator": "neq"},
1622
- ],
1623
- "field_string": [
1624
- {"name": "Starts with", "operator": "sw"},
1625
- {"name": "Ends with", "operator": "ew"},
1626
- {"name": "Contains", "operator": "ct"},
1627
- {"name": "Equal to", "operator": "eq"},
1628
- {"name": "Not Starts with", "operator": "nsw"},
1629
- {"name": "Not Ends with", "operator": "new"},
1630
- {"name": "Not Contains", "operator": "nct"},
1631
- {"name": "Not Equal to", "operator": "neq"},
1632
- ],
1633
- }
1634
- self.assertEqual(data["filters"], expected_filters)
1635
-
1636
- def test_info_fields(self):
1637
- """
1638
- REST Api: Test info fields (add, edit)
1639
- """
1640
- client = self.app.test_client()
1641
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1642
-
1643
- uri = "api/v1/model1apifieldsinfo/_info"
1644
- rv = self.auth_client_get(client, token, uri)
1645
- data = json.loads(rv.data.decode("utf-8"))
1646
- expect_add_fields = [
1647
- {
1648
- "description": "Field Integer",
1649
- "label": "Field Integer",
1650
- "name": "field_integer",
1651
- "required": False,
1652
- "unique": False,
1653
- "type": "Integer",
1654
- },
1655
- {
1656
- "description": "Field Float",
1657
- "label": "Field Float",
1658
- "name": "field_float",
1659
- "required": False,
1660
- "unique": False,
1661
- "type": "Float",
1662
- },
1663
- {
1664
- "description": "Field String",
1665
- "label": "Field String",
1666
- "name": "field_string",
1667
- "required": True,
1668
- "unique": True,
1669
- "type": "String",
1670
- "validate": ["<Length(min=None, max=50, equal=None, error=None)>"],
1671
- },
1672
- {
1673
- "description": "",
1674
- "label": "Field Date",
1675
- "name": "field_date",
1676
- "required": False,
1677
- "unique": False,
1678
- "type": "Date",
1679
- },
1680
- ]
1681
- expect_edit_fields = list()
1682
- for edit_col in self.model1apifieldsinfo.edit_columns:
1683
- for item in expect_add_fields:
1684
- if item["name"] == edit_col:
1685
- expect_edit_fields.append(item)
1686
- self.assertEqual(data[API_ADD_COLUMNS_RES_KEY], expect_add_fields)
1687
- self.assertEqual(data[API_EDIT_COLUMNS_RES_KEY], expect_edit_fields)
1688
-
1689
- def test_info_fields_rel_field(self):
1690
- """
1691
- REST Api: Test info fields with related fields
1692
- """
1693
- client = self.app.test_client()
1694
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1695
-
1696
- uri = "api/v1/model2api/_info"
1697
- rv = self.auth_client_get(client, token, uri)
1698
- data = json.loads(rv.data.decode("utf-8"))
1699
- expected_rel_add_field = {
1700
- "count": MODEL2_DATA_SIZE,
1701
- "description": "",
1702
- "label": "Group",
1703
- "name": "group",
1704
- "required": True,
1705
- "unique": False,
1706
- "type": "Related",
1707
- "values": [],
1708
- }
1709
- for i in range(self.model2api.page_size):
1710
- expected_rel_add_field["values"].append(
1711
- {"id": i + 1, "value": "test{}".format(i)}
1712
- )
1713
- for rel_field in data[API_ADD_COLUMNS_RES_KEY]:
1714
- if rel_field["name"] == "group":
1715
- self.assertEqual(rel_field, expected_rel_add_field)
1716
-
1717
- def test_info_fields_rel_filtered_field(self):
1718
- """
1719
- REST Api: Test info fields with filtered
1720
- related fields
1721
- """
1722
- client = self.app.test_client()
1723
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1724
- uri = "api/v1/model2apifilteredrelfields/_info"
1725
- rv = self.auth_client_get(client, token, uri)
1726
- data = json.loads(rv.data.decode("utf-8"))
1727
- expected_rel_add_field = {
1728
- "description": "",
1729
- "label": "Group",
1730
- "name": "group",
1731
- "required": True,
1732
- "unique": False,
1733
- "type": "Related",
1734
- "count": 1,
1735
- "values": [{"id": 4, "value": "test3"}],
1736
- }
1737
- for rel_field in data[API_ADD_COLUMNS_RES_KEY]:
1738
- if rel_field["name"] == "group":
1739
- self.assertEqual(rel_field, expected_rel_add_field)
1740
- for rel_field in data[API_EDIT_COLUMNS_RES_KEY]:
1741
- if rel_field["name"] == "group":
1742
- self.assertEqual(rel_field, expected_rel_add_field)
1743
-
1744
- def test_info_permissions(self):
1745
- """
1746
- REST Api: Test info permissions
1747
- """
1748
- client = self.app.test_client()
1749
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1750
- uri = "api/v1/model1api/_info"
1751
- rv = self.auth_client_get(client, token, uri)
1752
- data = json.loads(rv.data.decode("utf-8"))
1753
- expected_permissions = [
1754
- "can_delete",
1755
- "can_get",
1756
- "can_info",
1757
- "can_post",
1758
- "can_put",
1759
- ]
1760
- self.assertEqual(sorted(data[API_PERMISSIONS_RES_KEY]), expected_permissions)
1761
- uri = "api/v1/model1apirestrictedpermissions/_info"
1762
- rv = self.auth_client_get(client, token, uri)
1763
- data = json.loads(rv.data.decode("utf-8"))
1764
- expected_permissions = ["can_get", "can_info"]
1765
- self.assertEqual(sorted(data[API_PERMISSIONS_RES_KEY]), expected_permissions)
1766
-
1767
- def test_info_select_meta_data(self):
1768
- """
1769
- REST Api: Test info select meta data
1770
- """
1771
- # select meta for add fields
1772
- client = self.app.test_client()
1773
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1774
-
1775
- selectable_keys = [
1776
- API_ADD_COLUMNS_RIS_KEY,
1777
- API_EDIT_COLUMNS_RIS_KEY,
1778
- API_PERMISSIONS_RIS_KEY,
1779
- API_FILTERS_RIS_KEY,
1780
- API_ADD_TITLE_RIS_KEY,
1781
- API_EDIT_TITLE_RIS_KEY,
1782
- ]
1783
- for selectable_key in selectable_keys:
1784
- arguments = {API_SELECT_KEYS_RIS_KEY: [selectable_key]}
1785
- uri = f"api/v1/model1api/_info?{API_URI_RIS_KEY}={prison.dumps(arguments)}"
1786
- rv = self.auth_client_get(client, token, uri)
1787
- data = json.loads(rv.data.decode("utf-8"))
1788
- self.assertEqual(len(data.keys()), 1)
1789
- # We assume that rison meta key equals result meta key
1790
- assert selectable_key in data
1791
-
1792
- def test_delete_item(self):
1793
- """
1794
- REST Api: Test delete item
1795
- """
1796
- client = self.app.test_client()
1797
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1798
-
1799
- model = (
1800
- self.appbuilder.get_session.query(Model2)
1801
- .filter_by(field_string="test2")
1802
- .one_or_none()
1803
- )
1804
- pk = model.id
1805
- uri = f"api/v1/model2api/{pk}"
1806
- rv = self.auth_client_delete(client, token, uri)
1807
- self.assertEqual(rv.status_code, 200)
1808
- model = self.db.session.query(Model2).get(pk)
1809
- self.assertEqual(model, None)
1810
-
1811
- # Revert data changes
1812
- insert_model2(self.appbuilder.get_session, i=pk - 1)
1813
-
1814
- def test_delete_item_integrity(self):
1815
- """
1816
- REST Api: Test delete item integrity
1817
- """
1818
- client = self.app.test_client()
1819
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1820
-
1821
- model = (
1822
- self.appbuilder.get_session.query(Model1)
1823
- .filter_by(field_string="test0")
1824
- .one_or_none()
1825
- )
1826
- pk = model.id
1827
- uri = f"api/v1/model1api/{pk}"
1828
- rv = self.auth_client_delete(client, token, uri)
1829
- self.assertEqual(rv.status_code, 422)
1830
- model = self.db.session.query(Model1).get(pk)
1831
- self.assertIsNotNone(model)
1832
-
1833
- def test_delete_item_not_found(self):
1834
- """
1835
- REST Api: Test delete item not found
1836
- """
1837
- client = self.app.test_client()
1838
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1839
-
1840
- max_id = self.appbuilder.get_session.query(func.max(Model1.id)).scalar()
1841
- pk = max_id + 1
1842
- uri = f"api/v1/model1api/{pk}"
1843
- rv = self.auth_client_delete(client, token, uri)
1844
- self.assertEqual(rv.status_code, 404)
1845
-
1846
- def test_delete_item_base_filters(self):
1847
- """
1848
- REST Api: Test delete item with base filters
1849
- """
1850
- client = self.app.test_client()
1851
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1852
-
1853
- model = (
1854
- self.appbuilder.get_session.query(Model1)
1855
- .filter_by(field_integer=2)
1856
- .one_or_none()
1857
- )
1858
-
1859
- # Try to delete a filtered item
1860
- pk = model.id
1861
- uri = "api/v1/model1apifiltered/{}".format(pk)
1862
- rv = self.auth_client_delete(client, token, uri)
1863
- self.assertEqual(rv.status_code, 404)
1864
-
1865
- def test_update_item(self):
1866
- """
1867
- REST Api: Test update item
1868
- """
1869
- client = self.app.test_client()
1870
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1871
- model1 = (
1872
- self.appbuilder.get_session.query(Model1)
1873
- .filter_by(field_string="test2")
1874
- .one_or_none()
1875
- )
1876
- pk = model1.id
1877
- item = dict(field_string="test_Put", field_integer=0, field_float=0.0)
1878
- uri = f"api/v1/model1api/{pk}"
1879
- rv = self.auth_client_put(client, token, uri, item)
1880
- self.assertEqual(rv.status_code, 200)
1881
- model = self.db.session.query(Model1).get(pk)
1882
- self.assertEqual(model.field_string, "test_Put")
1883
- self.assertEqual(model.field_integer, 0)
1884
- self.assertEqual(model.field_float, 0.0)
1885
-
1886
- # Revert data changes
1887
- insert_model1(self.appbuilder.get_session, i=pk - 1)
1888
-
1889
- def test_update_custom_validation(self):
1890
- """
1891
- REST Api: Test update item custom validation
1892
- """
1893
- client = self.app.test_client()
1894
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1895
- model1 = (
1896
- self.appbuilder.get_session.query(Model1)
1897
- .filter_by(field_string="test2")
1898
- .one_or_none()
1899
- )
1900
- pk = model1.id
1901
- item = dict(field_string="test_Put", field_integer=0, field_float=0.0)
1902
- uri = f"api/v1/model1customvalidationapi/{pk}"
1903
- rv = self.auth_client_put(client, token, uri, item)
1904
- self.assertEqual(rv.status_code, 422)
1905
- pk = 3
1906
- item = dict(field_string="Atest_Put", field_integer=0, field_float=0.0)
1907
- uri = f"api/v1/model1customvalidationapi/{pk}"
1908
- rv = self.auth_client_put(client, token, uri, item)
1909
- self.assertEqual(rv.status_code, 200)
1910
-
1911
- # Revert data changes
1912
- insert_model1(self.appbuilder.get_session, i=pk - 1)
1913
-
1914
- def test_update_item_custom_schema(self):
1915
- """
1916
- REST Api: Test update item custom schema
1917
- """
1918
- from .sqla.models import Model1CustomSchema
1919
-
1920
- class Model1ApiCustomSchema(self.model1api):
1921
- edit_model_schema = Model1CustomSchema()
1922
-
1923
- self.appbuilder.add_api(Model1ApiCustomSchema)
1924
-
1925
- client = self.app.test_client()
1926
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1927
- # Test custom validation item must start with a capital A
1928
- item = dict(
1929
- field_string=f"test{MODEL1_DATA_SIZE + 1}",
1930
- field_integer=MODEL1_DATA_SIZE + 1,
1931
- field_float=float(MODEL1_DATA_SIZE + 1),
1932
- field_date=None,
1933
- )
1934
- uri = "api/v1/model1apicustomschema/1"
1935
- rv = self.auth_client_put(client, token, uri, item)
1936
- self.assertEqual(rv.status_code, 422)
1937
- data = json.loads(rv.data.decode("utf-8"))
1938
- self.assertEqual(
1939
- data, {"message": {"field_string": ["Name must start with an A"]}}
1940
- )
1941
-
1942
- # Test normal update with custom schema
1943
- item = dict(
1944
- field_string=f"Atest{MODEL1_DATA_SIZE + 1}",
1945
- field_integer=MODEL1_DATA_SIZE + 1,
1946
- field_float=float(MODEL1_DATA_SIZE + 1),
1947
- field_date=None,
1948
- )
1949
- uri = "api/v1/model1apicustomschema/1"
1950
- rv = self.auth_client_put(client, token, uri, item)
1951
- self.assertEqual(rv.status_code, 200)
1952
-
1953
- model = (
1954
- self.db.session.query(Model1)
1955
- .filter_by(field_string="Atest{}".format(MODEL1_DATA_SIZE + 1))
1956
- .first()
1957
- )
1958
- self.assertEqual(model.field_string, f"Atest{MODEL1_DATA_SIZE + 1}")
1959
- self.assertEqual(model.field_integer, MODEL1_DATA_SIZE + 1)
1960
- self.assertEqual(model.field_float, float(MODEL1_DATA_SIZE + 1))
1961
-
1962
- # Revert data changes
1963
- insert_model1(self.appbuilder.get_session, i=0)
1964
-
1965
- def test_update_item_base_filters(self):
1966
- """
1967
- REST Api: Test update item with base filters
1968
- """
1969
- client = self.app.test_client()
1970
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
1971
- model1 = (
1972
- self.appbuilder.get_session.query(Model1)
1973
- .filter_by(field_integer=3)
1974
- .one_or_none()
1975
- )
1976
- pk = model1.id
1977
- item = dict(field_string="test_Put", field_integer=3, field_float=3.0)
1978
- uri = f"api/v1/model1apifiltered/{pk}"
1979
- rv = self.auth_client_put(client, token, uri, item)
1980
- self.assertEqual(rv.status_code, 200)
1981
- model = self.db.session.query(Model1).get(pk)
1982
- self.assertEqual(model.field_string, "test_Put")
1983
- self.assertEqual(model.field_integer, 3)
1984
- self.assertEqual(model.field_float, 3.0)
1985
-
1986
- # Revert data changes
1987
- insert_model1(self.appbuilder.get_session, i=pk - 1)
1988
-
1989
- # We can't update an item that is base filtered
1990
- model1 = (
1991
- self.appbuilder.get_session.query(Model1)
1992
- .filter_by(field_integer=1)
1993
- .one_or_none()
1994
- )
1995
- pk = model1.id
1996
- uri = f"api/v1/model1apifiltered/{pk}"
1997
- rv = self.auth_client_put(client, token, uri, item)
1998
- self.assertEqual(rv.status_code, 404)
1999
-
2000
- def test_update_item_not_found(self):
2001
- """
2002
- REST Api: Test update item not found
2003
- """
2004
- client = self.app.test_client()
2005
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2006
-
2007
- max_id = self.appbuilder.get_session.query(func.max(Model1.id)).scalar()
2008
- pk = max_id + 1
2009
- item = dict(field_string="test_Put", field_integer=0, field_float=0.0)
2010
- uri = f"api/v1/model1api/{pk}"
2011
- rv = self.auth_client_put(client, token, uri, item)
2012
- self.assertEqual(rv.status_code, 404)
2013
-
2014
- def test_update_val_size(self):
2015
- """
2016
- REST Api: Test update validate size
2017
- """
2018
- client = self.app.test_client()
2019
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2020
- model1 = (
2021
- self.appbuilder.get_session.query(Model1)
2022
- .filter_by(field_string="test0")
2023
- .one_or_none()
2024
- )
2025
- pk = model1.id
2026
- field_string = "a" * 51
2027
- item = dict(field_string=field_string, field_integer=11, field_float=11.0)
2028
- uri = f"api/v1/model1api/{pk}"
2029
- rv = self.auth_client_put(client, token, uri, item)
2030
- self.assertEqual(rv.status_code, 422)
2031
- data = json.loads(rv.data.decode("utf-8"))
2032
- self.assertEqual(
2033
- data["message"]["field_string"][0], "Longer than maximum length 50."
2034
- )
2035
-
2036
- def test_update_mm_field(self):
2037
- """
2038
- REST Api: Test update m-m field
2039
- """
2040
- session = self.appbuilder.get_session
2041
- pk = 1
2042
- # Fetching children so that we can revert the changes
2043
- original_model = session.query(ModelMMParent).filter_by(id=pk).one_or_none()
2044
- original_children = [child for child in original_model.children]
2045
-
2046
- child = ModelMMChild()
2047
- child.field_string = "update_m,m"
2048
- session.add(child)
2049
- session.commit()
2050
-
2051
- child_id = (
2052
- session.query(ModelMMChild)
2053
- .filter_by(field_string="update_m,m")
2054
- .one_or_none()
2055
- .id
2056
- )
2057
- item = dict(children=[child_id])
2058
- client = self.app.test_client()
2059
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2060
- uri = "api/v1/modelmmapi/{}".format(pk)
2061
- rv = self.auth_client_put(client, token, uri, item)
2062
- self.assertEqual(rv.status_code, 200)
2063
- data = json.loads(rv.data.decode("utf-8"))
2064
- self.assertEqual(
2065
- data[API_RESULT_RES_KEY], {"children": [child_id], "field_string": "0"}
2066
- )
2067
-
2068
- # Revert data changes
2069
- original_model = session.query(ModelMMParent).filter_by(id=pk).one_or_none()
2070
- original_model.children = original_children
2071
- session.merge(original_model)
2072
- session.commit()
2073
- child = session.query(ModelMMChild).filter_by(id=child_id).one_or_none()
2074
- session.delete(child)
2075
- session.commit()
2076
-
2077
- def test_update_item_val_type(self):
2078
- """
2079
- REST Api: Test update validate type
2080
- """
2081
- client = self.app.test_client()
2082
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2083
-
2084
- model1 = (
2085
- self.appbuilder.get_session.query(Model1)
2086
- .filter_by(field_string="test0")
2087
- .one_or_none()
2088
- )
2089
- pk = model1.id
2090
- item = dict(
2091
- field_string=f"test{MODEL1_DATA_SIZE + 1}",
2092
- field_integer=f"test{MODEL1_DATA_SIZE + 1}",
2093
- field_float=11.0,
2094
- )
2095
- uri = f"api/v1/model1api/{pk}"
2096
- rv = self.auth_client_put(client, token, uri, item)
2097
- self.assertEqual(rv.status_code, 422)
2098
- data = json.loads(rv.data.decode("utf-8"))
2099
- self.assertEqual(data["message"]["field_integer"][0], "Not a valid integer.")
2100
-
2101
- item = dict(field_string=11, field_integer=11, field_float=11.0)
2102
- rv = self.auth_client_put(client, token, uri, item)
2103
- self.assertEqual(rv.status_code, 422)
2104
- data = json.loads(rv.data.decode("utf-8"))
2105
- self.assertEqual(data["message"]["field_string"][0], "Not a valid string.")
2106
-
2107
- def test_update_item_excluded_cols(self):
2108
- """
2109
- REST Api: Test update item with excluded cols
2110
- """
2111
- client = self.app.test_client()
2112
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2113
-
2114
- model1 = (
2115
- self.appbuilder.get_session.query(Model1)
2116
- .filter_by(field_string="test0")
2117
- .one_or_none()
2118
- )
2119
- pk = model1.id
2120
- item = dict(field_string="test_Put")
2121
- uri = f"api/v1/model1apiexcludecols/{pk}"
2122
- rv = self.auth_client_put(client, token, uri, item)
2123
- self.assertEqual(rv.status_code, 200)
2124
- model = self.db.session.query(Model1).get(pk)
2125
- self.assertEqual(model.field_integer, 0)
2126
- self.assertEqual(model.field_float, 0.0)
2127
- self.assertEqual(model.field_date, None)
2128
- self.assertEqual(model.field_string, "test_Put")
2129
-
2130
- item = dict(field_string="test_Put", field_integer=1000)
2131
- uri = f"api/v1/model1apiexcludecols/{pk}"
2132
- rv = self.auth_client_put(client, token, uri, item)
2133
- data = json.loads(rv.data.decode("utf-8"))
2134
- self.assertEqual(rv.status_code, 422)
2135
- expected_response = {"message": {"field_integer": ["Unknown field."]}}
2136
- self.assertEqual(expected_response, data)
2137
-
2138
- # Revert data changes
2139
- insert_model1(self.appbuilder.get_session, i=pk - 1)
2140
-
2141
- def test_create_item(self):
2142
- """
2143
- REST Api: Test create item
2144
- """
2145
- client = self.app.test_client()
2146
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2147
- item = dict(
2148
- field_string="test{}".format(MODEL1_DATA_SIZE + 1),
2149
- field_integer=MODEL1_DATA_SIZE + 1,
2150
- field_float=float(MODEL1_DATA_SIZE + 1),
2151
- field_date=None,
2152
- )
2153
- uri = "api/v1/model1api/"
2154
- rv = self.auth_client_post(client, token, uri, item)
2155
- data = json.loads(rv.data.decode("utf-8"))
2156
- self.assertEqual(rv.status_code, 201)
2157
- self.assertEqual(data[API_RESULT_RES_KEY], item)
2158
- model = (
2159
- self.db.session.query(Model1)
2160
- .filter_by(field_string="test{}".format(MODEL1_DATA_SIZE + 1))
2161
- .first()
2162
- )
2163
- self.assertEqual(model.field_string, f"test{MODEL1_DATA_SIZE + 1}")
2164
- self.assertEqual(model.field_integer, MODEL1_DATA_SIZE + 1)
2165
- self.assertEqual(model.field_float, float(MODEL1_DATA_SIZE + 1))
2166
-
2167
- # Revert data changes
2168
- self.appbuilder.get_session.delete(model)
2169
- self.appbuilder.get_session.commit()
2170
-
2171
- def test_create_item_bad_request(self):
2172
- """
2173
- REST Api: Test create item with bad request
2174
- """
2175
- client = self.app.test_client()
2176
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2177
- item = dict(
2178
- field_string="test{}".format(MODEL1_DATA_SIZE + 1),
2179
- field_integer=MODEL1_DATA_SIZE + 1,
2180
- field_float=float(MODEL1_DATA_SIZE + 1),
2181
- field_date=None,
2182
- )
2183
- uri = "api/v1/model1api/"
2184
- rv = client.post(
2185
- uri, data=item, headers={"Authorization": "Bearer {}".format(token)}
2186
- )
2187
- data = json.loads(rv.data.decode("utf-8"))
2188
- self.assertEqual(rv.status_code, 400)
2189
- self.assertEqual(data, {"message": "Request is not JSON"})
2190
-
2191
- def test_create_item_custom_validation(self):
2192
- """
2193
- REST Api: Test create item custom validation
2194
- """
2195
- client = self.app.test_client()
2196
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2197
- item = dict(
2198
- field_string=f"test{MODEL1_DATA_SIZE + 1}",
2199
- field_integer=MODEL1_DATA_SIZE + 1,
2200
- field_float=float(MODEL1_DATA_SIZE + 1),
2201
- field_date=None,
2202
- )
2203
- uri = "api/v1/model1customvalidationapi/"
2204
- rv = self.auth_client_post(client, token, uri, item)
2205
- data = json.loads(rv.data.decode("utf-8"))
2206
- self.assertEqual(rv.status_code, 422)
2207
- self.assertEqual(
2208
- data, {"message": {"field_string": ["Name must start with an A"]}}
2209
- )
2210
- item = dict(
2211
- field_string=f"A{MODEL1_DATA_SIZE + 1}",
2212
- field_integer=MODEL1_DATA_SIZE + 1,
2213
- field_float=float(MODEL1_DATA_SIZE + 1),
2214
- field_date=None,
2215
- )
2216
- rv = self.auth_client_post(client, token, uri, item)
2217
- data = json.loads(rv.data.decode("utf-8"))
2218
- self.assertEqual(rv.status_code, 201)
2219
-
2220
- # Revert test data
2221
- self.appbuilder.get_session.query(Model1).filter_by(
2222
- field_string=f"A{MODEL1_DATA_SIZE + 1}"
2223
- ).delete()
2224
- self.appbuilder.get_session.commit()
2225
-
2226
- def test_create_item_custom_schema(self):
2227
- """
2228
- REST Api: Test create item custom schema
2229
- """
2230
- from .sqla.models import Model1CustomSchema
2231
-
2232
- class Model1ApiCustomSchema(self.model1api):
2233
- add_model_schema = Model1CustomSchema()
2234
-
2235
- self.appbuilder.add_api(Model1ApiCustomSchema)
2236
-
2237
- client = self.app.test_client()
2238
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2239
- # Test custom validation item must start with a capital A
2240
- item = dict(
2241
- field_string=f"test{MODEL1_DATA_SIZE + 1}",
2242
- field_integer=MODEL1_DATA_SIZE + 1,
2243
- field_float=float(MODEL1_DATA_SIZE + 1),
2244
- field_date=None,
2245
- )
2246
- uri = "api/v1/model1apicustomschema/"
2247
- rv = self.auth_client_post(client, token, uri, item)
2248
- data = json.loads(rv.data.decode("utf-8"))
2249
- self.assertEqual(rv.status_code, 422)
2250
- self.assertEqual(
2251
- data, {"message": {"field_string": ["Name must start with an A"]}}
2252
- )
2253
-
2254
- item = dict(
2255
- field_string=f"Atest{MODEL1_DATA_SIZE + 1}",
2256
- field_integer=MODEL1_DATA_SIZE + 1,
2257
- field_float=float(MODEL1_DATA_SIZE + 1),
2258
- field_date=None,
2259
- )
2260
- uri = "api/v1/model1apicustomschema/"
2261
- rv = self.auth_client_post(client, token, uri, item)
2262
- data = json.loads(rv.data.decode("utf-8"))
2263
- self.assertEqual(rv.status_code, 201)
2264
-
2265
- model = (
2266
- self.db.session.query(Model1)
2267
- .filter_by(field_string="Atest{}".format(MODEL1_DATA_SIZE + 1))
2268
- .first()
2269
- )
2270
- self.assertEqual(model.field_string, f"Atest{MODEL1_DATA_SIZE + 1}")
2271
- self.assertEqual(model.field_integer, MODEL1_DATA_SIZE + 1)
2272
- self.assertEqual(model.field_float, float(MODEL1_DATA_SIZE + 1))
2273
-
2274
- # Revert data changes
2275
- self.appbuilder.get_session.delete(model)
2276
- self.appbuilder.get_session.commit()
2277
-
2278
- def test_create_item_val_size(self):
2279
- """
2280
- REST Api: Test create validate size
2281
- """
2282
- client = self.app.test_client()
2283
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2284
- field_string = "a" * 51
2285
- item = dict(
2286
- field_string=field_string,
2287
- field_integer=MODEL1_DATA_SIZE + 1,
2288
- field_float=float(MODEL1_DATA_SIZE + 1),
2289
- )
2290
- uri = "api/v1/model1api/"
2291
- rv = self.auth_client_post(client, token, uri, item)
2292
- self.assertEqual(rv.status_code, 422)
2293
- data = json.loads(rv.data.decode("utf-8"))
2294
- self.assertEqual(
2295
- data["message"]["field_string"][0], "Longer than maximum length 50."
2296
- )
2297
-
2298
- def test_create_item_val_type(self):
2299
- """
2300
- REST Api: Test create validate type
2301
- """
2302
- # Test integer as string
2303
- client = self.app.test_client()
2304
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2305
- item = dict(
2306
- field_string=f"test{MODEL1_DATA_SIZE}",
2307
- field_integer=f"test{MODEL1_DATA_SIZE}",
2308
- field_float=float(MODEL1_DATA_SIZE),
2309
- )
2310
- uri = "api/v1/model1api/"
2311
- rv = self.auth_client_post(client, token, uri, item)
2312
- self.assertEqual(rv.status_code, 422)
2313
- data = json.loads(rv.data.decode("utf-8"))
2314
- self.assertEqual(data["message"]["field_integer"][0], "Not a valid integer.")
2315
- # Test string as integer
2316
- item = dict(
2317
- field_string=MODEL1_DATA_SIZE,
2318
- field_integer=MODEL1_DATA_SIZE,
2319
- field_float=float(MODEL1_DATA_SIZE),
2320
- )
2321
- rv = self.auth_client_post(client, token, uri, item)
2322
- self.assertEqual(rv.status_code, 422)
2323
- data = json.loads(rv.data.decode("utf-8"))
2324
- self.assertEqual(data["message"]["field_string"][0], "Not a valid string.")
2325
-
2326
- def test_create_item_excluded_cols(self):
2327
- """
2328
- REST Api: Test create with excluded columns
2329
- """
2330
- client = self.app.test_client()
2331
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2332
- item = dict(field_string=f"test{MODEL1_DATA_SIZE + 1}")
2333
- uri = "api/v1/model1apiexcludecols/"
2334
- rv = self.auth_client_post(client, token, uri, item)
2335
- self.assertEqual(rv.status_code, 201)
2336
- model = (
2337
- self.db.session.query(Model1)
2338
- .filter_by(field_string=f"test{MODEL1_DATA_SIZE + 1}")
2339
- .first()
2340
- )
2341
- self.assertEqual(model.field_integer, None)
2342
- self.assertEqual(model.field_float, None)
2343
- self.assertEqual(model.field_date, None)
2344
-
2345
- item = dict(
2346
- field_string="test{}".format(MODEL1_DATA_SIZE + 2),
2347
- field_integer=MODEL1_DATA_SIZE + 2,
2348
- )
2349
- rv = self.auth_client_post(client, token, uri, item)
2350
- self.assertEqual(rv.status_code, 422)
2351
- data = json.loads(rv.data.decode("utf-8"))
2352
- expected_response = {"message": {"field_integer": ["Unknown field."]}}
2353
- self.assertEqual(data, expected_response)
2354
-
2355
- # Revert test data
2356
- self.appbuilder.get_session.query(Model1).filter_by(
2357
- field_string=f"test{MODEL1_DATA_SIZE + 1}"
2358
- ).delete()
2359
- self.appbuilder.get_session.query(Model1).filter_by(
2360
- field_string=f"test{MODEL1_DATA_SIZE + 2}"
2361
- ).delete()
2362
- self.appbuilder.get_session.commit()
2363
-
2364
- def test_create_item_with_enum(self):
2365
- """
2366
- REST Api: Test create item with enum
2367
- """
2368
- client = self.app.test_client()
2369
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2370
- item = dict(enum2="e1")
2371
- uri = "api/v1/modelwithenumsapi/"
2372
- rv = self.auth_client_post(client, token, uri, item)
2373
- data = json.loads(rv.data.decode("utf-8"))
2374
- self.assertEqual(rv.status_code, 201)
2375
- model = self.db.session.query(ModelWithEnums).get(data["id"])
2376
- self.assertEqual(model.enum2, TmpEnum.e1)
2377
-
2378
- # Revert test data
2379
- self.appbuilder.get_session.query(Model1).filter_by(
2380
- field_string=f"test{MODEL1_DATA_SIZE + 1}"
2381
- ).delete()
2382
- self.appbuilder.get_session.query(Model1).filter_by(
2383
- field_string=f"test{MODEL1_DATA_SIZE + 2}"
2384
- ).delete()
2385
- self.appbuilder.get_session.commit()
2386
-
2387
- def test_create_item_mm_field(self):
2388
- """
2389
- REST Api: Test create with M-M field
2390
- """
2391
- client = self.app.test_client()
2392
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2393
- item = dict(field_string="new1", children=[1, 2])
2394
- uri = "api/v1/modelmmapi/"
2395
- rv = self.auth_client_post(client, token, uri, item)
2396
- self.assertEqual(rv.status_code, 201)
2397
- data = json.loads(rv.data.decode("utf-8"))
2398
- self.assertEqual(
2399
- data[API_RESULT_RES_KEY], {"children": [1, 2], "field_string": "new1"}
2400
- )
2401
- # Test without M-M field data, default is not required
2402
- item = dict(field_string="new2")
2403
- uri = "api/v1/modelmmapi/"
2404
- rv = self.auth_client_post(client, token, uri, item)
2405
- self.assertEqual(rv.status_code, 201)
2406
- data = json.loads(rv.data.decode("utf-8"))
2407
- self.assertEqual(
2408
- data[API_RESULT_RES_KEY], {"children": [], "field_string": "new2"}
2409
- )
2410
- # Test without M-M field data, default is required
2411
- item = dict(field_string="new1")
2412
- uri = "api/v1/modelmmrequiredapi/"
2413
- rv = self.auth_client_post(client, token, uri, item)
2414
- self.assertEqual(rv.status_code, 422)
2415
- data = json.loads(rv.data.decode("utf-8"))
2416
- self.assertEqual(
2417
- data, {"message": {"children": ["Missing data for required field."]}}
2418
- )
2419
-
2420
- # Rollback data changes
2421
- model1 = (
2422
- self.appbuilder.get_session.query(ModelMMParent)
2423
- .filter_by(field_string="new1")
2424
- .one_or_none()
2425
- )
2426
- model2 = (
2427
- self.appbuilder.get_session.query(ModelMMParent)
2428
- .filter_by(field_string="new2")
2429
- .one_or_none()
2430
- )
2431
- self.appbuilder.get_session.delete(model1)
2432
- self.appbuilder.get_session.delete(model2)
2433
- self.appbuilder.get_session.commit()
2434
-
2435
- def test_create_item_om_field(self):
2436
- """
2437
- REST Api: Test create with O-M field
2438
- """
2439
- client = self.app.test_client()
2440
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2441
- child1 = ModelOMChild(field_string="child1")
2442
- child2 = ModelOMChild(field_string="child2")
2443
- self.appbuilder.get_session.add(child1)
2444
- self.appbuilder.get_session.add(child2)
2445
- self.appbuilder.get_session.commit()
2446
-
2447
- item = dict(field_string="new1", children=[child1.id, child2.id])
2448
- uri = "api/v1/modelomparentapi/"
2449
- rv = self.auth_client_post(client, token, uri, item)
2450
- self.assertEqual(rv.status_code, 201)
2451
- data = json.loads(rv.data.decode("utf-8"))
2452
- self.assertEqual(
2453
- data[API_RESULT_RES_KEY],
2454
- {"children": [child1.id, child2.id], "field_string": "new1"},
2455
- )
2456
- # Rollback data changes
2457
- model1 = (
2458
- self.appbuilder.get_session.query(ModelOMParent)
2459
- .filter_by(field_string="new1")
2460
- .one_or_none()
2461
- )
2462
-
2463
- self.appbuilder.get_session.delete(model1)
2464
- self.appbuilder.get_session.commit()
2465
-
2466
- def test_get_list_col_function(self):
2467
- """
2468
- REST Api: Test get list of objects with columns as functions
2469
- """
2470
- client = self.app.test_client()
2471
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2472
- uri = "api/v1/model1funcapi/"
2473
- rv = self.auth_client_get(client, token, uri)
2474
- self.assertEqual(rv.status_code, 200)
2475
- data = json.loads(rv.data.decode("utf-8"))
2476
- # Tests count property
2477
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
2478
- # Tests data result default page size
2479
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
2480
- for i in range(1, self.model1api.page_size):
2481
- item = data[API_RESULT_RES_KEY][i - 1]
2482
- self.assertEqual(
2483
- item["full_concat"], f"test{str(i - 1)}.{i - 1}.{float(i - 1)}.{None}"
2484
- )
2485
-
2486
- def test_get_list_col_property(self):
2487
- """
2488
- REST Api: Test get list of objects with columns as property
2489
- """
2490
- client = self.app.test_client()
2491
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2492
- uri = "api/v1/modelwithpropertyapi/"
2493
- rv = self.auth_client_get(client, token, uri)
2494
- self.assertEqual(rv.status_code, 200)
2495
- data = json.loads(rv.data.decode("utf-8"))
2496
- # Tests count property
2497
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
2498
- # Tests data result default page size
2499
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
2500
- for i in range(1, self.model1api.page_size):
2501
- item = data[API_RESULT_RES_KEY][i - 1]
2502
- self.assertEqual(item["custom_property"], f"{item['field_string']}_custom")
2503
-
2504
- def test_get_list_col_callable(self):
2505
- """
2506
- REST Api: Test get list of objects with columns as callable
2507
- """
2508
- client = self.app.test_client()
2509
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2510
- uri = "api/v1/model2callablecolapi/"
2511
- rv = self.auth_client_get(client, token, uri)
2512
- self.assertEqual(rv.status_code, 200)
2513
- data = json.loads(rv.data.decode("utf-8"))
2514
- # Tests count property
2515
- self.assertEqual(data["count"], MODEL1_DATA_SIZE)
2516
- # Tests data result default page size
2517
- self.assertEqual(len(data[API_RESULT_RES_KEY]), self.model1api.page_size)
2518
- results = data[API_RESULT_RES_KEY]
2519
- for i, item in enumerate(results):
2520
- self.assertEqual(
2521
- item["field_method"], f"{item['field_string']}_field_method"
2522
- )
2523
-
2524
- def test_openapi(self):
2525
- """
2526
- REST Api: Test OpenAPI spec
2527
- """
2528
- client = self.app.test_client()
2529
- token = self.login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2530
- uri = "api/v1/_openapi"
2531
- rv = self.auth_client_get(client, token, uri)
2532
- self.assertEqual(rv.status_code, 200)
2533
-
2534
- def test_swagger_ui(self):
2535
- """
2536
- REST Api: Test Swagger UI
2537
- """
2538
- client = self.app.test_client()
2539
- self.browser_login(client, USERNAME_ADMIN, PASSWORD_ADMIN)
2540
- uri = "swagger/v1"
2541
- rv = client.get(uri)
2542
- self.assertEqual(rv.status_code, 200)
2543
-
2544
- def test_class_method_permission_override(self):
2545
- """
2546
- REST Api: Test class method permission name override
2547
- """
2548
-
2549
- class Model2PermOverride1(ModelRestApi):
2550
- datamodel = SQLAInterface(Model2)
2551
- class_permission_name = "api"
2552
- method_permission_name = {
2553
- "get_list": "access",
2554
- "get": "access",
2555
- "put": "access",
2556
- "post": "access",
2557
- "delete": "access",
2558
- "info": "access",
2559
- }
2560
-
2561
- self.model2permoverride1 = Model2PermOverride1
2562
- self.appbuilder.add_api(Model2PermOverride1)
2563
-
2564
- role = self.appbuilder.sm.add_role("Test")
2565
- pvm = self.appbuilder.sm.find_permission_view_menu("can_access", "api")
2566
- self.appbuilder.sm.add_permission_role(role, pvm)
2567
- self.appbuilder.sm.add_user(
2568
- "test", "test", "user", "test@fab.org", role, "test"
2569
- )
2570
-
2571
- client = self.app.test_client()
2572
- token = self.login(client, "test", "test")
2573
- uri = "api/v1/model2permoverride1/"
2574
- rv = self.auth_client_get(client, token, uri)
2575
- self.assertEqual(rv.status_code, 200)
2576
- uri = "api/v1/model2permoverride1/_info"
2577
- rv = self.auth_client_get(client, token, uri)
2578
- self.assertEqual(rv.status_code, 200)
2579
- uri = "api/v1/model2permoverride1/1"
2580
- rv = self.auth_client_delete(client, token, uri)
2581
- self.assertEqual(rv.status_code, 200)
2582
-
2583
- # Revert test data
2584
- insert_model2(self.appbuilder.get_session, i=0)
2585
- self.appbuilder.get_session.delete(
2586
- self.appbuilder.sm.find_user(username="test")
2587
- )
2588
- self.appbuilder.get_session.delete(self.appbuilder.sm.find_role("Test"))
2589
- self.appbuilder.get_session.commit()
2590
-
2591
- def test_method_permission_override(self):
2592
- """
2593
- REST Api: Test method permission name override
2594
- """
2595
-
2596
- class Model2PermOverride2(ModelRestApi):
2597
- datamodel = SQLAInterface(Model2)
2598
- method_permission_name = {
2599
- "get_list": "read",
2600
- "get": "read",
2601
- "put": "write",
2602
- "post": "write",
2603
- "delete": "write",
2604
- "info": "read",
2605
- }
2606
-
2607
- self.model2permoverride2 = Model2PermOverride2
2608
- self.appbuilder.add_api(Model2PermOverride2)
2609
-
2610
- role = self.appbuilder.sm.add_role("Test")
2611
- pvm = self.appbuilder.sm.find_permission_view_menu(
2612
- "can_read", "Model2PermOverride2"
2613
- )
2614
- self.appbuilder.sm.add_permission_role(role, pvm)
2615
- self.appbuilder.sm.add_user(
2616
- "test", "test", "user", "test@fab.org", role, "test"
2617
- )
2618
-
2619
- client = self.app.test_client()
2620
- token = self.login(client, "test", "test")
2621
- uri = "api/v1/model2permoverride2/"
2622
- rv = self.auth_client_get(client, token, uri)
2623
- self.assertEqual(rv.status_code, 200)
2624
- uri = "api/v1/model2permoverride2/_info"
2625
- rv = self.auth_client_get(client, token, uri)
2626
- self.assertEqual(rv.status_code, 200)
2627
- uri = "api/v1/model2permoverride2/1"
2628
- rv = self.auth_client_delete(client, token, uri)
2629
- self.assertEqual(rv.status_code, 401)
2630
-
2631
- # Revert test data
2632
- self.appbuilder.get_session.delete(
2633
- self.appbuilder.sm.find_user(username="test")
2634
- )
2635
- self.appbuilder.get_session.delete(self.appbuilder.sm.find_role("Test"))
2636
- self.appbuilder.get_session.commit()
2637
-
2638
- def test_base_permission_override(self):
2639
- """
2640
- REST Api: Test base perms with permission name override
2641
- """
2642
-
2643
- class Model2PermOverride3(ModelRestApi):
2644
- datamodel = SQLAInterface(Model2)
2645
- method_permission_name = {
2646
- "get_list": "read",
2647
- "get": "read",
2648
- "put": "write",
2649
- "post": "write",
2650
- "delete": "write",
2651
- "info": "read",
2652
- }
2653
- base_permissions = ["can_write"]
2654
-
2655
- self.model2permoverride3 = Model2PermOverride3
2656
- self.appbuilder.add_api(Model2PermOverride3)
2657
-
2658
- pvm = self.appbuilder.sm.find_permission_view_menu(
2659
- "can_write", "Model2PermOverride3"
2660
- )
2661
- self.assertEqual(pvm.permission.name, "can_write")
2662
- pvm = self.appbuilder.sm.find_permission_view_menu(
2663
- "can_read", "Model2PermOverride3"
2664
- )
2665
- self.assertEqual(pvm, None)
2666
-
2667
- def test_permission_converge_compress(self):
2668
- """
2669
- REST Api: Test permission name converge compress
2670
- """
2671
-
2672
- class Model1PermConverge(ModelRestApi):
2673
- datamodel = SQLAInterface(Model1)
2674
- class_permission_name = "api2"
2675
- previous_class_permission_name = "Model1Api"
2676
- method_permission_name = {
2677
- "get_list": "access2",
2678
- "get": "access2",
2679
- "put": "access2",
2680
- "post": "access2",
2681
- "delete": "access2",
2682
- "info": "access2",
2683
- }
2684
-
2685
- self.appbuilder.add_api(Model1PermConverge)
2686
- role = self.appbuilder.sm.add_role("Test")
2687
- pvm = self.appbuilder.sm.find_permission_view_menu("can_get", "Model1Api")
2688
- self.appbuilder.sm.add_permission_role(role, pvm)
2689
- self.appbuilder.sm.add_user(
2690
- "test", "test", "user", "test@fab.org", role, "test"
2691
- )
2692
- # Remove previous class, Hack to test code change
2693
- for i, baseview in enumerate(self.appbuilder.baseviews):
2694
- if baseview.__class__.__name__ == "Model1Api":
2695
- break
2696
- self.appbuilder.baseviews.pop(i)
2697
- for i, baseview in enumerate(self.appbuilder.baseviews):
2698
- if baseview.__class__.__name__ == "Model1PermOverride":
2699
- break
2700
- self.appbuilder.baseviews.pop(i)
2701
-
2702
- target_state_transitions = {
2703
- "add": {
2704
- ("Model1Api", "can_get"): {("api2", "can_access2")},
2705
- ("Model1Api", "can_delete"): {("api2", "can_access2")},
2706
- ("Model1Api", "can_info"): {("api2", "can_access2")},
2707
- ("Model1Api", "can_put"): {("api2", "can_access2")},
2708
- ("Model1Api", "can_post"): {("api2", "can_access2")},
2709
- },
2710
- "del_role_pvm": {
2711
- ("Model1Api", "can_put"),
2712
- ("Model1Api", "can_delete"),
2713
- ("Model1Api", "can_get"),
2714
- ("Model1Api", "can_info"),
2715
- ("Model1Api", "can_post"),
2716
- },
2717
- "del_views": {"Model1Api"},
2718
- "del_perms": set(),
2719
- }
2720
- state_transitions = self.appbuilder.security_converge()
2721
- self.assertEqual(state_transitions, target_state_transitions)
2722
- role = self.appbuilder.sm.find_role("Test")
2723
- pvm = self.appbuilder.sm.find_permission_view_menu("can_access2", "api2")
2724
- assert pvm in role.permissions
2725
- self.assertEqual(len(role.permissions), 1)
2726
-
2727
- # Revert test data
2728
- self.appbuilder.get_session.delete(
2729
- self.appbuilder.sm.find_user(username="test")
2730
- )
2731
- self.appbuilder.get_session.delete(self.appbuilder.sm.find_role("Test"))
2732
- self.appbuilder.get_session.commit()
2733
-
2734
- def test_permission_converge_expand(self):
2735
- """
2736
- REST Api: Test permission name converge expand
2737
- """
2738
-
2739
- class Model1PermConverge(ModelRestApi):
2740
- datamodel = SQLAInterface(Model1)
2741
- class_permission_name = "Model1PermOverride"
2742
- previous_class_permission_name = "api"
2743
- method_permission_name = {
2744
- "get_list": "get",
2745
- "get": "get",
2746
- "put": "put",
2747
- "post": "post",
2748
- "delete": "delete",
2749
- "info": "info",
2750
- }
2751
- previous_method_permission_name = {
2752
- "get_list": "access",
2753
- "get": "access",
2754
- "put": "access",
2755
- "post": "access",
2756
- "delete": "access",
2757
- "info": "access",
2758
- }
2759
-
2760
- self.appbuilder.add_api(Model1PermConverge)
2761
- role = self.appbuilder.sm.add_role("Test")
2762
- pvm = self.appbuilder.sm.find_permission_view_menu("can_access", "api")
2763
- self.appbuilder.sm.add_permission_role(role, pvm)
2764
- self.appbuilder.sm.add_user(
2765
- "test", "test", "user", "test@fab.org", role, "test"
2766
- )
2767
- # Remove previous class, Hack to test code change
2768
- for i, baseview in enumerate(self.appbuilder.baseviews):
2769
- if baseview.__class__.__name__ == "Model1PermOverride":
2770
- break
2771
- self.appbuilder.baseviews.pop(i)
2772
-
2773
- target_state_transitions = {
2774
- "add": {
2775
- ("api", "can_access"): {
2776
- ("Model1PermOverride", "can_get"),
2777
- ("Model1PermOverride", "can_post"),
2778
- ("Model1PermOverride", "can_put"),
2779
- ("Model1PermOverride", "can_delete"),
2780
- ("Model1PermOverride", "can_info"),
2781
- }
2782
- },
2783
- "del_role_pvm": {("api", "can_access")},
2784
- "del_views": {"api"},
2785
- "del_perms": {"can_access"},
2786
- }
2787
- state_transitions = self.appbuilder.security_converge()
2788
- self.assertEqual(state_transitions, target_state_transitions)
2789
- role = self.appbuilder.sm.find_role("Test")
2790
- self.assertEqual(len(role.permissions), 5)