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,11 +1,15 @@
1
1
  import datetime
2
+ from typing import List, Optional
2
3
 
3
4
  from flask import g
5
+ from flask_appbuilder import Model
6
+ from flask_appbuilder._compat import as_unicode
4
7
  from sqlalchemy import (
5
8
  Boolean,
6
9
  Column,
7
10
  DateTime,
8
11
  ForeignKey,
12
+ Index,
9
13
  Integer,
10
14
  Sequence,
11
15
  String,
@@ -13,18 +17,30 @@ from sqlalchemy import (
13
17
  UniqueConstraint,
14
18
  )
15
19
  from sqlalchemy.ext.declarative import declared_attr
16
- from sqlalchemy.orm import backref, relationship
20
+ from sqlalchemy.orm import backref, Mapped, relationship
21
+
22
+ try:
23
+ from sqlalchemy.orm import mapped_column
24
+ except ImportError:
25
+ # fallback for SQLAlchemy < 2.0
26
+ def mapped_column(*args, **kwargs):
27
+ from sqlalchemy import Column
28
+
29
+ return Column(*args, **kwargs)
17
30
 
18
- from ... import Model
19
- from ..._compat import as_unicode
20
31
 
21
32
  _dont_audit = False
22
33
 
23
34
 
24
35
  class Permission(Model):
25
36
  __tablename__ = "ab_permission"
26
- id = Column(Integer, Sequence("ab_permission_id_seq"), primary_key=True)
27
- name = Column(String(100), unique=True, nullable=False)
37
+
38
+ id: Mapped[int] = mapped_column(
39
+ Integer,
40
+ Sequence("ab_permission_id_seq", start=1, increment=1, minvalue=1, cycle=False),
41
+ primary_key=True,
42
+ )
43
+ name: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
28
44
 
29
45
  def __repr__(self):
30
46
  return self.name
@@ -32,8 +48,13 @@ class Permission(Model):
32
48
 
33
49
  class ViewMenu(Model):
34
50
  __tablename__ = "ab_view_menu"
35
- id = Column(Integer, Sequence("ab_view_menu_id_seq"), primary_key=True)
36
- name = Column(String(250), unique=True, nullable=False)
51
+
52
+ id: Mapped[int] = mapped_column(
53
+ Integer,
54
+ Sequence("ab_view_menu_id_seq", start=1, increment=1, minvalue=1, cycle=False),
55
+ primary_key=True,
56
+ )
57
+ name: Mapped[str] = mapped_column(String(250), unique=True, nullable=False)
37
58
 
38
59
  def __eq__(self, other):
39
60
  return (isinstance(other, self.__class__)) and (self.name == other.name)
@@ -48,20 +69,62 @@ class ViewMenu(Model):
48
69
  assoc_permissionview_role = Table(
49
70
  "ab_permission_view_role",
50
71
  Model.metadata,
51
- Column("id", Integer, Sequence("ab_permission_view_role_id_seq"), primary_key=True),
52
- Column("permission_view_id", Integer, ForeignKey("ab_permission_view.id")),
53
- Column("role_id", Integer, ForeignKey("ab_role.id")),
72
+ Column(
73
+ "id",
74
+ Integer,
75
+ Sequence(
76
+ "ab_permission_view_role_id_seq",
77
+ start=1,
78
+ increment=1,
79
+ minvalue=1,
80
+ cycle=False,
81
+ ),
82
+ primary_key=True,
83
+ ),
84
+ Column(
85
+ "permission_view_id",
86
+ Integer,
87
+ ForeignKey("ab_permission_view.id", ondelete="CASCADE"),
88
+ ),
89
+ Column("role_id", Integer, ForeignKey("ab_role.id", ondelete="CASCADE")),
54
90
  UniqueConstraint("permission_view_id", "role_id"),
91
+ Index("idx_permission_view_id", "permission_view_id"),
92
+ Index("idx_role_id", "role_id"),
93
+ )
94
+
95
+
96
+ assoc_user_role = Table(
97
+ "ab_user_role",
98
+ Model.metadata,
99
+ Column(
100
+ "id",
101
+ Integer,
102
+ Sequence("ab_user_role_id_seq", start=1, increment=1, minvalue=1, cycle=False),
103
+ primary_key=True,
104
+ ),
105
+ Column("user_id", Integer, ForeignKey("ab_user.id", ondelete="CASCADE")),
106
+ Column("role_id", Integer, ForeignKey("ab_role.id", ondelete="CASCADE")),
107
+ UniqueConstraint("user_id", "role_id"),
55
108
  )
56
109
 
57
110
 
58
111
  class Role(Model):
59
112
  __tablename__ = "ab_role"
60
113
 
61
- id = Column(Integer, Sequence("ab_role_id_seq"), primary_key=True)
62
- name = Column(String(64), unique=True, nullable=False)
63
- permissions = relationship(
64
- "PermissionView", secondary=assoc_permissionview_role, backref="role"
114
+ id: Mapped[int] = mapped_column(
115
+ Integer,
116
+ Sequence("ab_role_id_seq", start=1, increment=1, minvalue=1, cycle=False),
117
+ primary_key=True,
118
+ )
119
+ name: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
120
+ permissions: Mapped[List["PermissionView"]] = relationship(
121
+ "PermissionView",
122
+ secondary=assoc_permissionview_role,
123
+ backref="role",
124
+ passive_deletes=True,
125
+ )
126
+ user: Mapped[List["User"]] = relationship(
127
+ "User", secondary=assoc_user_role, backref="roles", enable_typechecks=False
65
128
  )
66
129
 
67
130
  def __repr__(self):
@@ -70,63 +133,72 @@ class Role(Model):
70
133
 
71
134
  class PermissionView(Model):
72
135
  __tablename__ = "ab_permission_view"
73
- __table_args__ = (UniqueConstraint("permission_id", "view_menu_id"),)
74
- id = Column(Integer, Sequence("ab_permission_view_id_seq"), primary_key=True)
75
- permission_id = Column(Integer, ForeignKey("ab_permission.id"))
76
- permission = relationship("Permission")
77
- view_menu_id = Column(Integer, ForeignKey("ab_view_menu.id"))
78
- view_menu = relationship("ViewMenu")
136
+ __table_args__ = (
137
+ UniqueConstraint("permission_id", "view_menu_id"),
138
+ Index("idx_permission_id", "permission_id"),
139
+ Index("idx_view_menu_id", "view_menu_id"),
140
+ )
141
+ id: Mapped[int] = mapped_column(
142
+ Integer,
143
+ Sequence(
144
+ "ab_permission_view_id_seq", start=1, increment=1, minvalue=1, cycle=False
145
+ ),
146
+ primary_key=True,
147
+ )
148
+ permission_id: Mapped[int] = mapped_column(Integer, ForeignKey("ab_permission.id"))
149
+ permission: Mapped[Permission] = relationship("Permission", lazy="joined")
150
+ view_menu_id: Mapped[int] = mapped_column(Integer, ForeignKey("ab_view_menu.id"))
151
+ view_menu: Mapped[ViewMenu] = relationship("ViewMenu", lazy="joined")
79
152
 
80
153
  def __repr__(self):
81
- return str(self.permission).replace("_", " ") + " on " + str(self.view_menu)
82
-
83
-
84
- assoc_user_role = Table(
85
- "ab_user_role",
86
- Model.metadata,
87
- Column("id", Integer, Sequence("ab_user_role_id_seq"), primary_key=True),
88
- Column("user_id", Integer, ForeignKey("ab_user.id")),
89
- Column("role_id", Integer, ForeignKey("ab_role.id")),
90
- UniqueConstraint("user_id", "role_id"),
91
- )
154
+ return str(self.permission).replace("_", " ") + f" on {str(self.view_menu)}"
92
155
 
93
156
 
94
157
  class User(Model):
95
158
  __tablename__ = "ab_user"
96
- id = Column(Integer, Sequence("ab_user_id_seq"), primary_key=True)
97
- first_name = Column(String(64), nullable=False)
98
- last_name = Column(String(64), nullable=False)
99
- username = Column(String(64), unique=True, nullable=False)
100
- password = Column(String(256))
101
- active = Column(Boolean)
102
- email = Column(String(64), unique=True, nullable=False)
103
- last_login = Column(DateTime)
104
- login_count = Column(Integer)
105
- fail_login_count = Column(Integer)
106
- roles = relationship("Role", secondary=assoc_user_role, backref="user")
107
- created_on = Column(DateTime, default=datetime.datetime.now, nullable=True)
108
- changed_on = Column(DateTime, default=datetime.datetime.now, nullable=True)
159
+ id: Mapped[int] = mapped_column(
160
+ Integer,
161
+ Sequence("ab_user_id_seq", start=1, increment=1, minvalue=1, cycle=False),
162
+ primary_key=True,
163
+ )
164
+ first_name: Mapped[str] = mapped_column(String(64), nullable=False)
165
+ last_name: Mapped[str] = mapped_column(String(64), nullable=False)
166
+ username: Mapped[str] = mapped_column(String(128), unique=True, nullable=False)
167
+ password: Mapped[Optional[str]] = mapped_column(String(256))
168
+ active: Mapped[Optional[bool]] = mapped_column(Boolean, default=True)
169
+ email: Mapped[str] = mapped_column(String(320), unique=True, nullable=False)
170
+ last_login: Mapped[Optional[datetime.datetime]] = mapped_column(
171
+ DateTime, nullable=True
172
+ )
173
+ login_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
174
+ fail_login_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
175
+ created_on: Mapped[Optional[datetime.datetime]] = mapped_column(
176
+ DateTime, default=lambda: datetime.datetime.now(), nullable=True
177
+ )
178
+ changed_on: Mapped[Optional[datetime.datetime]] = mapped_column(
179
+ DateTime, default=lambda: datetime.datetime.now(), nullable=True
180
+ )
109
181
 
110
182
  @declared_attr
111
- def created_by_fk(self):
183
+ def created_by_fk(self) -> Column:
112
184
  return Column(
113
185
  Integer, ForeignKey("ab_user.id"), default=self.get_user_id, nullable=True
114
186
  )
115
187
 
116
188
  @declared_attr
117
- def changed_by_fk(self):
189
+ def changed_by_fk(self) -> Column:
118
190
  return Column(
119
191
  Integer, ForeignKey("ab_user.id"), default=self.get_user_id, nullable=True
120
192
  )
121
193
 
122
- created_by = relationship(
194
+ created_by: Mapped["User"] = relationship(
123
195
  "User",
124
196
  backref=backref("created", uselist=True),
125
197
  remote_side=[id],
126
198
  primaryjoin="User.created_by_fk == User.id",
127
199
  uselist=False,
128
200
  )
129
- changed_by = relationship(
201
+ changed_by: Mapped["User"] = relationship(
130
202
  "User",
131
203
  backref=backref("changed", uselist=True),
132
204
  remote_side=[id],
@@ -157,19 +229,82 @@ class User(Model):
157
229
  return as_unicode(self.id)
158
230
 
159
231
  def get_full_name(self):
160
- return u"{0} {1}".format(self.first_name, self.last_name)
232
+ return "{0} {1}".format(self.first_name, self.last_name)
161
233
 
162
234
  def __repr__(self):
163
235
  return self.get_full_name()
164
236
 
165
237
 
238
+ assoc_user_group = Table(
239
+ "ab_user_group",
240
+ Model.metadata,
241
+ Column(
242
+ "id",
243
+ Integer,
244
+ Sequence("ab_user_group_id_seq", start=1, increment=1, minvalue=1, cycle=False),
245
+ primary_key=True,
246
+ ),
247
+ Column("user_id", Integer, ForeignKey("ab_user.id", ondelete="CASCADE")),
248
+ Column("group_id", Integer, ForeignKey("ab_group.id", ondelete="CASCADE")),
249
+ UniqueConstraint("user_id", "group_id"),
250
+ Index("idx_user_id", "user_id"),
251
+ Index("idx_user_group_id", "group_id"),
252
+ )
253
+
254
+
255
+ assoc_group_role = Table(
256
+ "ab_group_role",
257
+ Model.metadata,
258
+ Column(
259
+ "id",
260
+ Integer,
261
+ Sequence("ab_group_role_id_seq", start=1, increment=1, minvalue=1, cycle=False),
262
+ primary_key=True,
263
+ ),
264
+ Column("group_id", Integer, ForeignKey("ab_group.id", ondelete="CASCADE")),
265
+ Column("role_id", Integer, ForeignKey("ab_role.id", ondelete="CASCADE")),
266
+ UniqueConstraint("group_id", "role_id"),
267
+ Index("idx_group_id", "group_id"),
268
+ Index("idx_group_role_id", "role_id"),
269
+ )
270
+
271
+
272
+ class Group(Model):
273
+ __tablename__ = "ab_group"
274
+ id: Mapped[int] = mapped_column(
275
+ Integer,
276
+ Sequence("ab_group_id_seq", start=1, increment=1, minvalue=1, cycle=False),
277
+ primary_key=True,
278
+ )
279
+ name: Mapped[str] = Column(String(100), unique=True, nullable=False)
280
+ label: Mapped[str] = Column(String(150))
281
+ description: Mapped[str] = Column(String(512))
282
+ users: Mapped[list[User]] = relationship(
283
+ "User", secondary=assoc_user_group, backref="groups", passive_deletes=True
284
+ )
285
+ roles: Mapped[list[Role]] = relationship(
286
+ "Role", secondary=assoc_group_role, backref="groups", passive_deletes=True
287
+ )
288
+
289
+ def __repr__(self):
290
+ return self.name
291
+
292
+
166
293
  class RegisterUser(Model):
167
294
  __tablename__ = "ab_register_user"
168
- id = Column(Integer, Sequence("ab_register_user_id_seq"), primary_key=True)
169
- first_name = Column(String(64), nullable=False)
170
- last_name = Column(String(64), nullable=False)
171
- username = Column(String(64), unique=True, nullable=False)
172
- password = Column(String(256))
173
- email = Column(String(64), nullable=False)
174
- registration_date = Column(DateTime, default=datetime.datetime.now, nullable=True)
175
- registration_hash = Column(String(256))
295
+ id = mapped_column(
296
+ Integer,
297
+ Sequence(
298
+ "ab_register_user_id_seq", start=1, increment=1, minvalue=1, cycle=False
299
+ ),
300
+ primary_key=True,
301
+ )
302
+ first_name: Mapped[str] = mapped_column(String(64), nullable=False)
303
+ last_name: Mapped[str] = mapped_column(String(64), nullable=False)
304
+ username: Mapped[str] = mapped_column(String(128), unique=True, nullable=False)
305
+ password: Mapped[Optional[str]] = mapped_column(String(256))
306
+ email: Mapped[str] = mapped_column(String(320), unique=True, nullable=False)
307
+ registration_date: Mapped[Optional[datetime.datetime]] = mapped_column(
308
+ DateTime, default=lambda: datetime.datetime.now(), nullable=True
309
+ )
310
+ registration_hash: Mapped[Optional[str]] = mapped_column(String(256))
@@ -0,0 +1,9 @@
1
+ from random import SystemRandom
2
+ import string
3
+
4
+ LETTERS_AND_DIGITS = string.ascii_letters + string.digits
5
+
6
+
7
+ def generate_random_string(length=30):
8
+ rand = SystemRandom()
9
+ return "".join(rand.choice(LETTERS_AND_DIGITS) for _ in range(length))