Flask-Exts 0.2.3__tar.gz → 0.2.4__tar.gz

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 (396) hide show
  1. {flask_exts-0.2.3 → flask_exts-0.2.4}/CHANGES.rst +11 -1
  2. {flask_exts-0.2.3 → flask_exts-0.2.4}/PKG-INFO +4 -1
  3. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/configure.rst +1 -1
  4. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/index.rst +1 -0
  5. flask_exts-0.2.4/docs/user.rst +11 -0
  6. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/build_sample.py +4 -4
  7. flask_exts-0.2.4/examples/demo/templates/my/index.html +7 -0
  8. {flask_exts-0.2.3 → flask_exts-0.2.4}/pyproject.toml +4 -1
  9. flask_exts-0.2.4/src/flask_exts/__about__.py +1 -0
  10. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/base_admin.py +10 -1
  11. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/commands.py +5 -5
  12. flask_exts-0.2.4/src/flask_exts/constants.py +6 -0
  13. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/user.py +10 -5
  14. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/user_profile.py +3 -5
  15. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/webauthn.py +4 -1
  16. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/orm.py +2 -3
  17. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/base.py +33 -10
  18. flask_exts-0.2.4/src/flask_exts/email/reset_password_sender.py +12 -0
  19. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/manager.py +7 -22
  20. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/proxies.py +2 -2
  21. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/core.py +6 -0
  22. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/email_verification.py +6 -5
  23. flask_exts-0.2.4/src/flask_exts/security/reset_password.py +78 -0
  24. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/serializer.py +2 -2
  25. flask_exts-0.2.4/src/flask_exts/security/two_factor_authentication.py +38 -0
  26. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/modal.js +1 -1
  27. flask_exts-0.2.4/src/flask_exts/static/js/qrcode.js +1945 -0
  28. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/base.py +8 -19
  29. flask_exts-0.2.4/src/flask_exts/template/dataset.py +9 -0
  30. flask_exts-0.2.4/src/flask_exts/template/forms/change_password.py +28 -0
  31. flask_exts-0.2.4/src/flask_exts/template/forms/forgot_password.py +19 -0
  32. {flask_exts-0.2.3/src/flask_exts → flask_exts-0.2.4/src/flask_exts/template}/forms/login.py +1 -1
  33. {flask_exts-0.2.3/src/flask_exts → flask_exts-0.2.4/src/flask_exts/template}/forms/profile.py +1 -1
  34. flask_exts-0.2.4/src/flask_exts/template/forms/recovery.py +10 -0
  35. {flask_exts-0.2.3/src/flask_exts → flask_exts-0.2.4/src/flask_exts/template}/forms/register.py +1 -1
  36. flask_exts-0.2.4/src/flask_exts/template/forms/reset_password.py +16 -0
  37. flask_exts-0.2.4/src/flask_exts/template/forms/two_factor.py +10 -0
  38. flask_exts-0.2.4/src/flask_exts/template/funcs.py +39 -0
  39. flask_exts-0.2.4/src/flask_exts/template/plugin.py +18 -0
  40. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/theme.py +5 -12
  41. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/base.html +3 -3
  42. flask_exts-0.2.4/src/flask_exts/templates/admin/master.html +1 -0
  43. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/list.html +2 -2
  44. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/base.html +4 -6
  45. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap4/form.html +4 -4
  46. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap5/form.html +6 -6
  47. flask_exts-0.2.4/src/flask_exts/templates/layout.html +12 -0
  48. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/actions.html +2 -2
  49. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/form.html +1 -1
  50. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/icon.html +2 -2
  51. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/row_actions.html +2 -2
  52. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/table.html +8 -7
  53. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/vendor.html +0 -3
  54. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/rediscli/response.html +1 -1
  55. flask_exts-0.2.4/src/flask_exts/templates/views/user/change_password.html +11 -0
  56. flask_exts-0.2.4/src/flask_exts/templates/views/user/forgot_password.html +11 -0
  57. flask_exts-0.2.4/src/flask_exts/templates/views/user/index.html +21 -0
  58. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/user/login.html +4 -3
  59. flask_exts-0.2.4/src/flask_exts/templates/views/user/recovery.html +10 -0
  60. flask_exts-0.2.4/src/flask_exts/templates/views/user/recovery_codes.html +32 -0
  61. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/user/register.html +1 -1
  62. flask_exts-0.2.4/src/flask_exts/templates/views/user/reset_password.html +11 -0
  63. flask_exts-0.2.4/src/flask_exts/templates/views/user/setup_tfa.html +44 -0
  64. flask_exts-0.2.4/src/flask_exts/templates/views/user/show_tfa.html +38 -0
  65. flask_exts-0.2.4/src/flask_exts/templates/views/user/verify_tfa.html +11 -0
  66. flask_exts-0.2.4/src/flask_exts/templates/views/user/verify_tfa_modal.html +17 -0
  67. flask_exts-0.2.4/src/flask_exts/usercenter/base_user.py +14 -0
  68. flask_exts-0.2.4/src/flask_exts/usercenter/base_user_store.py +29 -0
  69. flask_exts-0.2.4/src/flask_exts/usercenter/core.py +43 -0
  70. flask_exts-0.2.3/src/flask_exts/usercenter/sqla_usercenter.py → flask_exts-0.2.4/src/flask_exts/usercenter/sqla_user_store.py +10 -5
  71. flask_exts-0.2.4/src/flask_exts/utils/image.py +12 -0
  72. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/request_user.py +4 -4
  73. flask_exts-0.2.4/src/flask_exts/views/user_view.py +300 -0
  74. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/test_casbin_auth.py +14 -14
  75. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/test_core.py +11 -3
  76. flask_exts-0.2.4/tests/security/test_tfa.py +18 -0
  77. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/test_base.py +1 -1
  78. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/usercenter/test_sqla_usercenter.py +2 -2
  79. flask_exts-0.2.4/tests/utils/test_image.py +7 -0
  80. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/utils/test_request_user.py +2 -2
  81. flask_exts-0.2.4/tests/views/test_user_view.py +483 -0
  82. flask_exts-0.2.3/examples/demo/templates/my/index.html +0 -7
  83. flask_exts-0.2.3/src/flask_exts/__about__.py +0 -1
  84. flask_exts-0.2.3/src/flask_exts/template/utils.py +0 -35
  85. flask_exts-0.2.3/src/flask_exts/templates/admin/master.html +0 -1
  86. flask_exts-0.2.3/src/flask_exts/templates/layout.html +0 -21
  87. flask_exts-0.2.3/src/flask_exts/templates/views/user/index.html +0 -7
  88. flask_exts-0.2.3/src/flask_exts/usercenter/base_user.py +0 -7
  89. flask_exts-0.2.3/src/flask_exts/usercenter/base_usercenter.py +0 -40
  90. flask_exts-0.2.3/src/flask_exts/views/user_view.py +0 -132
  91. flask_exts-0.2.3/tests/views/test_user_view.py +0 -96
  92. {flask_exts-0.2.3 → flask_exts-0.2.4}/.github/workflows/release.yml +0 -0
  93. {flask_exts-0.2.3 → flask_exts-0.2.4}/.gitignore +0 -0
  94. {flask_exts-0.2.3 → flask_exts-0.2.4}/.readthedocs.yaml +0 -0
  95. {flask_exts-0.2.3 → flask_exts-0.2.4}/LICENSE.rst +0 -0
  96. {flask_exts-0.2.3 → flask_exts-0.2.4}/README.rst +0 -0
  97. {flask_exts-0.2.3 → flask_exts-0.2.4}/babel/babel.cfg +0 -0
  98. {flask_exts-0.2.3 → flask_exts-0.2.4}/babel/babel.sh +0 -0
  99. {flask_exts-0.2.3 → flask_exts-0.2.4}/babel/messages.pot +0 -0
  100. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/Makefile +0 -0
  101. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/_static/logo.png +0 -0
  102. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/changes.rst +0 -0
  103. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/conf.py +0 -0
  104. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/develop.rst +0 -0
  105. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/examples.rst +0 -0
  106. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/make.bat +0 -0
  107. {flask_exts-0.2.3 → flask_exts-0.2.4}/docs/requirements.txt +0 -0
  108. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/README.rst +0 -0
  109. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/app.py +0 -0
  110. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/static/favicon.ico +0 -0
  111. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/bootswatch.html +0 -0
  112. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/flash.html +0 -0
  113. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/form.html +0 -0
  114. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/icon.html +0 -0
  115. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/icons.html +0 -0
  116. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/index.html +0 -0
  117. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/mybase.html +0 -0
  118. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/nav.html +0 -0
  119. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/pagination.html +0 -0
  120. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap4/templates/table.html +0 -0
  121. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/app.py +0 -0
  122. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/static/favicon.ico +0 -0
  123. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/bootswatch.html +0 -0
  124. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/flash.html +0 -0
  125. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/form.html +0 -0
  126. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/icon.html +0 -0
  127. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/icons.html +0 -0
  128. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/index.html +0 -0
  129. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/mybase.html +0 -0
  130. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/nav.html +0 -0
  131. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/pagination.html +0 -0
  132. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/bootstrap5/templates/table.html +0 -0
  133. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/__init__.py +0 -0
  134. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/admin_views.py +0 -0
  135. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/file_op.py +0 -0
  136. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/make_app.py +0 -0
  137. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/__init__.py +0 -0
  138. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/author.py +0 -0
  139. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/keyword.py +0 -0
  140. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/location_image.py +0 -0
  141. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/post.py +0 -0
  142. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/post_tag.py +0 -0
  143. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/tag.py +0 -0
  144. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/tree.py +0 -0
  145. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/user.py +0 -0
  146. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/models/user_keyword.py +0 -0
  147. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/templates/field_list.html +0 -0
  148. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/templates/locations.html +0 -0
  149. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/templates/tree_list.html +0 -0
  150. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/author_view.py +0 -0
  151. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/keyword_view.py +0 -0
  152. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/location_image_view.py +0 -0
  153. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/my_view.py +0 -0
  154. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/post_view.py +0 -0
  155. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/tag_view.py +0 -0
  156. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/tree_view.py +0 -0
  157. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/demo/views/user_view.py +0 -0
  158. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/fileadmin/__init__.py +0 -0
  159. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/fileadmin/file_view.py +0 -0
  160. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/fileadmin/make_app.py +0 -0
  161. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/fileadmin/tmp/bbb/a.txt +0 -0
  162. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/instance/config.py +0 -0
  163. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/rediscli.py +0 -0
  164. {flask_exts-0.2.3 → flask_exts-0.2.4}/examples/simple.py +0 -0
  165. {flask_exts-0.2.3 → flask_exts-0.2.4}/requirements/build.in +0 -0
  166. {flask_exts-0.2.3 → flask_exts-0.2.4}/requirements/test.in +0 -0
  167. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/__init__.py +0 -0
  168. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/__init__.py +0 -0
  169. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/actions.py +0 -0
  170. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/admin.py +0 -0
  171. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/menu.py +0 -0
  172. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/__init__.py +0 -0
  173. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/ajax.py +0 -0
  174. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/filters.py +0 -0
  175. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/form.py +0 -0
  176. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/typefmt.py +0 -0
  177. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/model/view.py +0 -0
  178. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/row_action.py +0 -0
  179. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/__init__.py +0 -0
  180. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/ajax.py +0 -0
  181. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/filters.py +0 -0
  182. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/form.py +0 -0
  183. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/typefmt.py +0 -0
  184. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/sqla/view.py +0 -0
  185. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/admin/view.py +0 -0
  186. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/babel/__init__.py +0 -0
  187. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/babel/selector.py +0 -0
  188. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/__init__.py +0 -0
  189. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/__init__.py +0 -0
  190. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/__init__.py +0 -0
  191. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/datastore/sqla/models/role.py +0 -0
  192. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/__init__.py +0 -0
  193. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/sender.py +0 -0
  194. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/smtp.py +0 -0
  195. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/email/verify_email_sender.py +0 -0
  196. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/__init__.py +0 -0
  197. {flask_exts-0.2.3/src/flask_exts/forms → flask_exts-0.2.4/src/flask_exts/security/authorizer}/__init__.py +0 -0
  198. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/authorizer/base.py +0 -0
  199. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/authorizer/casbin_authorizer.py +0 -0
  200. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/authorizer/sqlalchemy_adapter.py +0 -0
  201. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/hasher.py +0 -0
  202. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/principal/identity.py +0 -0
  203. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/principal/identity_context.py +0 -0
  204. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/principal/permission.py +0 -0
  205. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/security/principal/principal.py +0 -0
  206. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/signals.py +0 -0
  207. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/css/admin.css +0 -0
  208. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/css/rediscli.css +0 -0
  209. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/actions.js +0 -0
  210. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/details_filter.js +0 -0
  211. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/filters.js +0 -0
  212. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/form.js +0 -0
  213. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/helper.js +0 -0
  214. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/rediscli.js +0 -0
  215. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/security/base64.js +0 -0
  216. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/js/security/webauthn.js +0 -0
  217. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/logo.png +0 -0
  218. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/bootstrap-icons.svg +0 -0
  219. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/bootstrap-icons.css +0 -0
  220. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/bootstrap-icons.json +0 -0
  221. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/bootstrap-icons.min.css +0 -0
  222. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/bootstrap-icons.scss +0 -0
  223. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/fonts/bootstrap-icons.woff +0 -0
  224. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap-icons/font/fonts/bootstrap-icons.woff2 +0 -0
  225. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap4/bootstrap.bundle.min.js +0 -0
  226. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap4/bootstrap.min.css +0 -0
  227. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap5/bootstrap.bundle.min.js +0 -0
  228. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/bootstrap5/bootstrap.min.css +0 -0
  229. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/daterangepicker/daterangepicker.css +0 -0
  230. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/daterangepicker/daterangepicker.js +0 -0
  231. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/jquery/jquery.min.js +0 -0
  232. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/moment.min.js +0 -0
  233. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/select2/select2.min.css +0 -0
  234. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/select2/select2.min.js +0 -0
  235. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/vanilla-editor/css/bootstrap-editable.css +0 -0
  236. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/vanilla-editor/img/clear.png +0 -0
  237. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/vanilla-editor/img/loading.gif +0 -0
  238. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/static/vendor/vanilla-editor/js/bootstrap-editable.min.js +0 -0
  239. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/__init__.py +0 -0
  240. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/__init__.py +0 -0
  241. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/ajax_select.py +0 -0
  242. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/datetime.py +0 -0
  243. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/file.py +0 -0
  244. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/inline.py +0 -0
  245. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/json.py +0 -0
  246. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/recaptcha.py +0 -0
  247. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/select.py +0 -0
  248. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/sqla.py +0 -0
  249. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/switch.py +0 -0
  250. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/fields/taglist.py +0 -0
  251. {flask_exts-0.2.3/src/flask_exts/security/authorizer → flask_exts-0.2.4/src/flask_exts/template/form}/__init__.py +0 -0
  252. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/base_form.py +0 -0
  253. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/csrf.py +0 -0
  254. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/flask_form.py +0 -0
  255. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/form_opts.py +0 -0
  256. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/meta.py +0 -0
  257. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/session_csrf.py +0 -0
  258. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/form/utils.py +0 -0
  259. {flask_exts-0.2.3/src/flask_exts/template/form → flask_exts-0.2.4/src/flask_exts/template/forms}/__init__.py +0 -0
  260. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/rules.py +0 -0
  261. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/__init__.py +0 -0
  262. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/field_list.py +0 -0
  263. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/file.py +0 -0
  264. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/recaptcha.py +0 -0
  265. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/validators/sqla.py +0 -0
  266. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/__init__.py +0 -0
  267. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/checkbox.py +0 -0
  268. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/datetime.py +0 -0
  269. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/file.py +0 -0
  270. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/inline.py +0 -0
  271. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/recaptcha.py +0 -0
  272. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/render_template.py +0 -0
  273. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/select.py +0 -0
  274. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/template/widgets/xeditable.py +0 -0
  275. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/index.html +0 -0
  276. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/create.html +0 -0
  277. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/details.html +0 -0
  278. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/edit.html +0 -0
  279. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/modals/create.html +0 -0
  280. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/modals/details.html +0 -0
  281. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/admin/model/modals/edit.html +0 -0
  282. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap4/message.html +0 -0
  283. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap4/pagination.html +0 -0
  284. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/bootstrap5/message.html +0 -0
  285. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/index.html +0 -0
  286. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/inline.html +0 -0
  287. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/layout.html +0 -0
  288. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/menu.html +0 -0
  289. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/message.html +0 -0
  290. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/nav.html +0 -0
  291. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/macro/pagination.html +0 -0
  292. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/file/form.html +0 -0
  293. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/file/list.html +0 -0
  294. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/file/modals/form.html +0 -0
  295. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/rediscli/console.html +0 -0
  296. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/user/list.html +0 -0
  297. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/views/user/verify_email.html +0 -0
  298. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/widgets/inline_field_list.html +0 -0
  299. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/templates/widgets/inline_form.html +0 -0
  300. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/__init__.py +0 -0
  301. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/en/LC_MESSAGES/messages.mo +0 -0
  302. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/en/LC_MESSAGES/messages.po +0 -0
  303. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/zh_CN/LC_MESSAGES/messages.mo +0 -0
  304. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/translations/zh_CN/LC_MESSAGES/messages.po +0 -0
  305. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/types.py +0 -0
  306. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/usercenter/__init__.py +0 -0
  307. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/__init__.py +0 -0
  308. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/authorize.py +0 -0
  309. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/decorators.py +0 -0
  310. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/flask.py +0 -0
  311. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/jwt.py +0 -0
  312. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/sqla.py +0 -0
  313. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/tools.py +0 -0
  314. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/utils/url.py +0 -0
  315. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/file_view/__init__.py +0 -0
  316. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/file_view/base_file_view.py +0 -0
  317. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/file_view/local_file_storage.py +0 -0
  318. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/file_view/local_file_view.py +0 -0
  319. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/index_view.py +0 -0
  320. {flask_exts-0.2.3 → flask_exts-0.2.4}/src/flask_exts/views/rediscli_view.py +0 -0
  321. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/__init__.py +0 -0
  322. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/__init__.py +0 -0
  323. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/customer/conftest.py +0 -0
  324. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/customer/templates/mock.html +0 -0
  325. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/customer/test_admin.py +0 -0
  326. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/__init__.py +0 -0
  327. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/templates/another_macro.html +0 -0
  328. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/templates/macro.html +0 -0
  329. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_basic.py +0 -0
  330. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_form_rules.py +0 -0
  331. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_inlineform.py +0 -0
  332. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_multi_pk.py +0 -0
  333. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/sqla/test_translation.py +0 -0
  334. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/test_base.py +0 -0
  335. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/admin/test_model.py +0 -0
  336. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/conftest.py +0 -0
  337. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.gif +0 -0
  338. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.jpeg +0 -0
  339. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.jpg +0 -0
  340. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.png +0 -0
  341. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/data/copyleft.tiff +0 -0
  342. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/email/test_base.py +0 -0
  343. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/email/test_smtp.py +0 -0
  344. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/helper.py +0 -0
  345. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/__init__.py +0 -0
  346. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/message.py +0 -0
  347. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/model1.py +0 -0
  348. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/multpk.py +0 -0
  349. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/polymorphic.py +0 -0
  350. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/tree.py +0 -0
  351. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/models/user.py +0 -0
  352. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/__init__.py +0 -0
  353. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_adapter/rbac_model.conf +0 -0
  354. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_adapter/rbac_policy.csv +0 -0
  355. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_adapter/test_sqlalchemy_adapter.py +0 -0
  356. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_files/authz_policy.csv +0 -0
  357. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_files/rbac_model.conf +0 -0
  358. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/authorizer/casbin_files/rbac_policy.csv +0 -0
  359. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/security/test_principal.py +0 -0
  360. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/__init__.py +0 -0
  361. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/__init__.py +0 -0
  362. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_field.py +0 -0
  363. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_form.py +0 -0
  364. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_form_row.py +0 -0
  365. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_hidden_errors.py +0 -0
  366. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_icon_sprite.py +0 -0
  367. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_messages.py +0 -0
  368. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_pager.py +0 -0
  369. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_pagination.py +0 -0
  370. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap4/test_render_table.py +0 -0
  371. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/__init__.py +0 -0
  372. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/conftest.py +0 -0
  373. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_base.py +0 -0
  374. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_render_form.py +0 -0
  375. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_render_form_row.py +0 -0
  376. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_render_messages.py +0 -0
  377. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/bootstrap5/test_render_pagination.py +0 -0
  378. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/conftest.py +0 -0
  379. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/forms/test_csrf.py +0 -0
  380. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/forms/test_fields.py +0 -0
  381. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/forms/test_filefield.py +0 -0
  382. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/forms/test_uploadfile.py +0 -0
  383. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/macros/test_render_breadcrumb_item.py +0 -0
  384. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/macros/test_render_nav_item.py +0 -0
  385. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/template/test_base.py +0 -0
  386. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/templates/method.html +0 -0
  387. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/templates/mock.html +0 -0
  388. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/test_babel.py +0 -0
  389. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/test_flask.py +0 -0
  390. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/translations/en/LC_MESSAGES/messages.mo +0 -0
  391. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/translations/en/LC_MESSAGES/messages.po +0 -0
  392. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/translations/zh_CN/LC_MESSAGES/messages.po +0 -0
  393. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/utils/test_url.py +0 -0
  394. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/views/files/dummy.txt +0 -0
  395. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/views/test_file_view.py +0 -0
  396. {flask_exts-0.2.3 → flask_exts-0.2.4}/tests/views/test_rediscli_view.py +0 -0
@@ -1,3 +1,14 @@
1
+ v0.2.4
2
+ ------
3
+
4
+ Unreleased
5
+
6
+ - user change password
7
+ - user forgot password
8
+ - user 2FA with TOTP
9
+ - user recovery codes
10
+ - user recovery
11
+
1
12
  v0.2.3
2
13
  ------
3
14
 
@@ -7,7 +18,6 @@ Released 2025-08-28
7
18
  - add folder email
8
19
  - add security.email_verification
9
20
 
10
-
11
21
  v0.2.2
12
22
  ------
13
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Flask-Exts
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Flask extensions with admin, SQLAlchemy, babel, forms, fields, widgets, and so on.
5
5
  Project-URL: Documentation, https://flask-exts.readthedocs.io
6
6
  Project-URL: Source, https://github.com/ojso/flask-exts
@@ -46,7 +46,10 @@ Requires-Dist: flask-babel>=4.0
46
46
  Requires-Dist: flask-login>=0.6
47
47
  Requires-Dist: flask-sqlalchemy>=3.0
48
48
  Requires-Dist: flask>=3.0
49
+ Requires-Dist: pillow>=11.0
49
50
  Requires-Dist: pyjwt>=2.0
51
+ Requires-Dist: pyotp>=2.0
52
+ Requires-Dist: qrcode>=8.0
50
53
  Requires-Dist: tablib>=3.0
51
54
  Requires-Dist: webauthn>=2.0
52
55
  Requires-Dist: wtforms>=3.0
@@ -18,7 +18,7 @@ Configuration
18
18
  Default is ``csrf_token``.
19
19
  ``CSRF_TIME_LIMIT`` Max age in seconds for CSRF tokens.
20
20
  Default is ``1800``.
21
- ``VERIFY_EMAIL_SENDER`` Email address used to send verification emails.
21
+ ``NOREPLY_EMAIL_SENDER`` Email address used to send emails.
22
22
  It is a dict for smtp with keys: host,port,user,password.
23
23
  Default is None.
24
24
  ========================== =====================================================================
@@ -42,6 +42,7 @@ More examples, please click :doc:`examples`.
42
42
 
43
43
  configure
44
44
  develop
45
+ user
45
46
  examples
46
47
  changes
47
48
 
@@ -0,0 +1,11 @@
1
+ =======
2
+ User
3
+ =======
4
+
5
+ views
6
+ =======
7
+
8
+ .. automodule:: flask_exts.views.user_view
9
+ :members:
10
+ :undoc-members:
11
+ :show-inheritance:
@@ -1,6 +1,6 @@
1
1
  import random
2
2
  import datetime
3
- from flask_exts.proxies import _usercenter
3
+ from flask_exts.proxies import _userstore
4
4
  from .models import db
5
5
  from .models.author import Author
6
6
  from .models.author import AVAILABLE_USER_TYPES
@@ -13,11 +13,11 @@ from .models.location_image import ImageType
13
13
 
14
14
 
15
15
  def build_user_admin():
16
- u, _ = _usercenter.create_user(
16
+ u, _ = _userstore.create_user(
17
17
  username="admin", password="admin", email="admin@example.com"
18
18
  )
19
- r, _ = _usercenter.create_role(name="admin")
20
- _usercenter.user_add_role(u, r)
19
+ r, _ = _userstore.create_role(name="admin")
20
+ _userstore.user_add_role(u, r)
21
21
 
22
22
 
23
23
  def build_sample_image_type():
@@ -0,0 +1,7 @@
1
+ {% extends 'admin/master.html' %}
2
+ {% block main %}
3
+ <div>
4
+ Demo:my:index
5
+ </div>
6
+ <div> x= {{ x }}, x'type is {{ template.funcs.type_name(x) }}.</div>
7
+ {% endblock %}
@@ -33,8 +33,11 @@ dependencies = [
33
33
  "Flask-Login>=0.6",
34
34
  "tablib>=3.0",
35
35
  "casbin>=1.0",
36
- "PyJWT>=2.0",
36
+ "PyJWT>=2.0",
37
+ "pyotp>=2.0",
37
38
  "webauthn>=2.0",
39
+ "qrcode>=8.0",
40
+ "pillow>=11.0",
38
41
  ]
39
42
 
40
43
  [project.optional-dependencies]
@@ -0,0 +1 @@
1
+ __version__ = "0.2.4"
@@ -1,5 +1,6 @@
1
1
  from flask import render_template
2
2
  from flask import url_for
3
+ from flask import make_response
3
4
  from .menu import Menu
4
5
 
5
6
 
@@ -122,4 +123,12 @@ class BaseAdmin:
122
123
  # Add URL generation method to kwargs
123
124
  kwargs["get_url"] = self.get_url
124
125
 
125
- return render_template(template, **kwargs)
126
+ # If _headers is provided in kwargs, use make_response to set headers
127
+ if "_headers" in kwargs:
128
+ headers = kwargs.pop("_headers")
129
+ response = make_response(render_template(template, **kwargs))
130
+ for key, value in headers.items():
131
+ response.headers[key] = value
132
+ return response
133
+ else:
134
+ return render_template(template, **kwargs)
@@ -1,6 +1,6 @@
1
1
  import click
2
2
  from flask.cli import AppGroup
3
- from .proxies import _usercenter
3
+ from .proxies import _userstore
4
4
  from .datastore.sqla import db
5
5
 
6
6
 
@@ -20,15 +20,15 @@ security_cli = AppGroup("security", short_help="security for the app.")
20
20
  @click.argument("name")
21
21
  def create_user(name):
22
22
  print(f"security create_user {name}")
23
- result = _usercenter.create_user(username=name)
23
+ result = _userstore.create_user(username=name)
24
24
  click.echo(result)
25
25
 
26
26
 
27
27
  @security_cli.command("create_admin", help="create user:admin with admin:role")
28
28
  @click.argument("password", default="admin")
29
29
  def create_admin(password):
30
- u, _ = _usercenter.create_user(username="admin", password=password)
31
- r, _ = _usercenter.create_role(name="admin")
32
- _usercenter.user_add_role(u, r)
30
+ u, _ = _userstore.create_user(username="admin", password=password)
31
+ r, _ = _userstore.create_role(name="admin")
32
+ _userstore.user_add_role(u, r)
33
33
 
34
34
  print(f"security create admin {u.username} with role {r.name} ")
@@ -0,0 +1,6 @@
1
+ # no-cache header
2
+ NO_CACHE_HEADER = {
3
+ "Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
4
+ "Pragma": "no-cache",
5
+ "Expires": "0",
6
+ }
@@ -10,6 +10,8 @@ from ..orm import relationship
10
10
  from ..orm import ForeignKey
11
11
  from ..orm import Table
12
12
  from ..orm import Column
13
+ from ..orm import MutableList
14
+ from ..orm import JSON
13
15
  from .role import Role
14
16
  from .user_profile import UserProfile
15
17
 
@@ -28,31 +30,34 @@ class User(db.Model, BaseUser):
28
30
  uuid: Mapped[str] = mapped_column(unique=True, default=lambda: str(uuid.uuid4()))
29
31
  username: Mapped[Optional[str]] = mapped_column(unique=True)
30
32
  password: Mapped[Optional[str]]
31
- display_name: Mapped[Optional[str]]
32
- avatar: Mapped[Optional[str]]
33
+ actived: Mapped[bool] = mapped_column(default=False)
33
34
  status: Mapped[int] = mapped_column(default=0)
34
35
  expired_at: Mapped[Optional[datetime]]
35
- actived: Mapped[bool] = mapped_column(default=False)
36
36
  email: Mapped[Optional[str]] = mapped_column(unique=True)
37
37
  email_verified: Mapped[bool] = mapped_column(default=False)
38
38
  email_verified_at: Mapped[Optional[datetime]]
39
39
  phone_number: Mapped[Optional[str]] = mapped_column(unique=True)
40
40
  phone_verified: Mapped[bool] = mapped_column(default=False)
41
41
  phone_verified_at: Mapped[Optional[datetime]]
42
+ tfa_enabled: Mapped[bool] = mapped_column(default=False)
43
+ tfa_method: Mapped[Optional[str]]
44
+ totp_secret: Mapped[Optional[str]]
45
+ recovery_codes: Mapped[Optional[list[str]]] = mapped_column(
46
+ type_=MutableList.as_mutable(JSON)
47
+ )
42
48
  created_at: Mapped[datetime] = mapped_column(default=datetime.now)
43
49
  updated_at: Mapped[datetime] = mapped_column(
44
50
  default=datetime.now, onupdate=datetime.now
45
51
  )
46
52
 
47
53
  roles: Mapped[List["Role"]] = relationship(secondary=user_role_table)
48
-
49
54
  profile: Mapped["UserProfile"] = relationship(
50
55
  "UserProfile", back_populates="user", uselist=False
51
56
  )
52
57
 
53
58
  def get_roles(self):
54
59
  return [r.name for r in self.roles]
55
-
60
+
56
61
  @property
57
62
  def is_active(self):
58
63
  return self.actived
@@ -14,12 +14,10 @@ class UserProfile(db.Model):
14
14
  user_id: Mapped[int] = mapped_column(ForeignKey("user.id"), unique=True)
15
15
  name: Mapped[Optional[str]]
16
16
  identity: Mapped[Optional[str]] = mapped_column(unique=True)
17
+ display_name: Mapped[Optional[str]]
18
+ avatar: Mapped[Optional[str]]
17
19
  locale: Mapped[Optional[str]]
18
- timezone: Mapped[Optional[str]]
19
- # 2FA
20
- tf_method: Mapped[Optional[str]]
21
- tf_totp_secret: Mapped[Optional[str]]
22
- recovery_codes: Mapped[Optional[str]]
20
+ timezone: Mapped[Optional[str]]
23
21
  # timestamps
24
22
  created_at: Mapped[datetime] = mapped_column(default=datetime.now)
25
23
  updated_at: Mapped[datetime] = mapped_column(
@@ -8,6 +8,7 @@ from ..orm import LargeBinary
8
8
  from ..orm import ForeignKey
9
9
  from ..orm import relationship
10
10
  from ..orm import JSON
11
+ from ..orm import MutableList
11
12
 
12
13
 
13
14
  class WebAuthnMixin(db.Model):
@@ -19,7 +20,9 @@ class WebAuthnMixin(db.Model):
19
20
  )
20
21
  public_key: Mapped[bytes] = mapped_column(LargeBinary)
21
22
  sign_count: Mapped[Optional[int]] = mapped_column(default=0)
22
- transports: Mapped[Optional[list[str]]] = mapped_column(type_=JSON)
23
+ transports: Mapped[Optional[list[str]]] = mapped_column(
24
+ type_=MutableList.as_mutable(JSON)
25
+ )
23
26
  backup_state: Mapped[bool] = mapped_column()
24
27
  device_type: Mapped[str] = mapped_column()
25
28
  extensions: Mapped[Optional[str]] = mapped_column()
@@ -7,6 +7,5 @@ from sqlalchemy.orm import mapped_column
7
7
  from sqlalchemy.orm import declared_attr
8
8
  from sqlalchemy.types import LargeBinary
9
9
  from sqlalchemy.types import JSON
10
-
11
-
12
-
10
+ from sqlalchemy.ext.mutable import MutableDict
11
+ from sqlalchemy.ext.mutable import MutableList
@@ -1,5 +1,7 @@
1
1
  from ..signals import to_send_email
2
2
  from .verify_email_sender import VerifyEmailSender
3
+ from .reset_password_sender import ResetPasswordSender
4
+
3
5
 
4
6
  class Email:
5
7
  def __init__(self, app=None):
@@ -11,6 +13,15 @@ class Email:
11
13
  def init_app(self, app):
12
14
  self.app = app
13
15
  self.subscribe_signal(app)
16
+ self.register_senders()
17
+ if "verify_email" not in self.senders:
18
+ app.logger.warning(
19
+ "Please configure the NOREPLY_EMAIL_SENDER in your app config."
20
+ )
21
+ if "reset_password" not in self.senders:
22
+ app.logger.warning(
23
+ "Please configure the NOREPLY_EMAIL_SENDER in your app config."
24
+ )
14
25
 
15
26
  def subscribe_signal(self, app):
16
27
  to_send_email.connect(self.send, app)
@@ -21,23 +32,35 @@ class Email:
21
32
  return
22
33
  self.send_email(data["type"], data)
23
34
 
24
- def register_sender(self, name, sender):
25
- """Register a sender with a name."""
26
- if name in self.senders:
27
- raise ValueError(f"Sender with name {name} already exists.")
28
- self.senders[name] = sender
29
-
30
35
  def send_email(self, name, data):
31
36
  """Send an email using a registered sender."""
32
37
  if name not in self.senders:
33
- self.app.logger.warning(f"Email can not send data: {data}, because it can not find the sender: {name}")
38
+ self.app.logger.warning(
39
+ f"Email can not send data: {data}, because it can not find the sender: {name}"
40
+ )
34
41
  # raise ValueError(f"Email can not find the sender: {name}")
35
42
  return
36
43
  sender = self.senders.get(name)
37
44
  return sender.send(data)
38
-
45
+
39
46
  def register_senders(self):
40
47
  """Register multiple senders."""
41
- if "VERIFY_EMAIL_SENDER" in self.app.config:
42
- verify_email_sender = VerifyEmailSender(**self.app.config["VERIFY_EMAIL_SENDER"])
48
+ # Register VerifyEmailSender
49
+ if "NOREPLY_EMAIL_SENDER" in self.app.config:
50
+ verify_email_sender = VerifyEmailSender(
51
+ **self.app.config["NOREPLY_EMAIL_SENDER"]
52
+ )
43
53
  self.register_sender("verify_email", verify_email_sender)
54
+
55
+ # Register ResetPasswordSender
56
+ if "NOREPLY_EMAIL_SENDER" in self.app.config:
57
+ reset_password_sender = ResetPasswordSender(
58
+ **self.app.config["NOREPLY_EMAIL_SENDER"]
59
+ )
60
+ self.register_sender("reset_password", reset_password_sender)
61
+
62
+ def register_sender(self, name, sender):
63
+ """Register a sender with a name."""
64
+ if name in self.senders:
65
+ raise ValueError(f"Sender with name {name} already exists.")
66
+ self.senders[name] = sender
@@ -0,0 +1,12 @@
1
+ from .smtp import SmtpSSL
2
+
3
+
4
+ class ResetPasswordSender(SmtpSSL):
5
+ def create_emsg(self, data):
6
+ if "subject" not in data:
7
+ data["subject"] = "Please reset your password"
8
+ if "content" not in data:
9
+ data["content"] = data.get("reset_password_link", "")
10
+ if "to" not in data:
11
+ data["to"] = data.get("email")
12
+ return super().create_emsg(data)
@@ -1,10 +1,8 @@
1
- from flask_login import LoginManager
2
1
  from .datastore.sqla import db
3
2
  from .babel import babel_init_app
4
3
  from .template.base import Template
5
4
  from .email.base import Email
6
- from .usercenter.sqla_usercenter import SqlaUserCenter
7
- from .utils.request_user import load_user_from_request
5
+ from .usercenter.core import UserCenter
8
6
  from .security.core import Security
9
7
 
10
8
 
@@ -22,8 +20,10 @@ class Manager:
22
20
  def get_email(self):
23
21
  return Email()
24
22
 
25
- def get_usercenter(self):
26
- return SqlaUserCenter()
23
+ def get_admin_class(self):
24
+ from .admin.base_admin import BaseAdmin
25
+
26
+ return BaseAdmin
27
27
 
28
28
  def init_app(self, app):
29
29
  self.app = app
@@ -54,21 +54,11 @@ class Manager:
54
54
  # init email
55
55
  self.email = self.get_email()
56
56
  self.email.init_app(app)
57
- self.email.register_senders()
58
57
 
59
- # init usercenter
60
- self.usercenter = self.get_usercenter()
58
+ # init usercenter and initial login manager in usercenter
59
+ self.usercenter = UserCenter()
61
60
  self.usercenter.init_app(app)
62
61
 
63
- # login
64
- if not hasattr(app, "login_manager"):
65
- login_manager = LoginManager()
66
- login_manager.init_app(app)
67
- login_manager.login_view = "user.login"
68
- # login_manager.login_message = "Please login in"
69
- login_manager.user_loader(self.usercenter.user_loader)
70
- login_manager.request_loader(load_user_from_request)
71
-
72
62
  # init security
73
63
  self.security = Security()
74
64
  self.security.init_app(app)
@@ -76,8 +66,3 @@ class Manager:
76
66
  # init admin
77
67
  self.admin = self.get_admin_class()()
78
68
  self.admin.init_app(app)
79
-
80
- def get_admin_class(self):
81
- from .admin.base_admin import BaseAdmin
82
-
83
- return BaseAdmin
@@ -6,7 +6,7 @@ from werkzeug.local import LocalProxy
6
6
  if t.TYPE_CHECKING:
7
7
  from .manager import Manager
8
8
  from .template.base import Template
9
- from .usercenter.base_usercenter import BaseUserCenter
9
+ from .usercenter.base_user_store import BaseUserStore
10
10
  from .security.core import Security
11
11
  from .security.authorizer.base import Authorizer
12
12
 
@@ -15,7 +15,7 @@ _manager: "Manager" = LocalProxy(lambda: current_app.extensions["manager"])
15
15
 
16
16
  _template: "Template" = LocalProxy(lambda: _manager.template)
17
17
 
18
- _usercenter: "BaseUserCenter" = LocalProxy(lambda: _manager.usercenter)
18
+ _userstore: "BaseUserStore" = LocalProxy(lambda: _manager.usercenter.userstore)
19
19
 
20
20
  _security: "Security" = LocalProxy(lambda: _manager.security)
21
21
 
@@ -2,6 +2,8 @@ from .hasher import Blake2bHasher
2
2
  from .serializer import TimedUrlSerializer
3
3
  from .authorizer.casbin_authorizer import CasbinAuthorizer
4
4
  from .email_verification import EmailVerification
5
+ from .reset_password import ResetPassword
6
+ from .two_factor_authentication import TwoFactorAuthentication
5
7
 
6
8
 
7
9
  class Security:
@@ -19,8 +21,12 @@ class Security:
19
21
  self.serializer = TimedUrlSerializer(secret_key)
20
22
  # email verification
21
23
  self.email_verification = EmailVerification(app)
24
+ # reset password
25
+ self.reset_password = ResetPassword(app)
22
26
  # authorizer
23
27
  self.authorizer = CasbinAuthorizer(app)
28
+ # 2FA
29
+ self.tfa = TwoFactorAuthentication(app)
24
30
 
25
31
  def get_within(self, serializer_name):
26
32
  """Get the max age for a serializer."""
@@ -2,7 +2,7 @@ from datetime import datetime
2
2
  from flask import url_for
3
3
  from flask import current_app
4
4
  from ..proxies import _security
5
- from ..proxies import _usercenter
5
+ from ..proxies import _userstore
6
6
  from ..signals import to_send_email
7
7
 
8
8
 
@@ -30,7 +30,8 @@ class EmailVerification:
30
30
 
31
31
  :param user: The user to send the instructions to
32
32
  """
33
-
33
+ if user is None or user.email is None:
34
+ return
34
35
  token = self.generate_verify_email_token(user)
35
36
  link = url_for("user.verify_email", token=token, _external=True)
36
37
 
@@ -44,7 +45,7 @@ class EmailVerification:
44
45
 
45
46
  to_send_email.send(current_app._get_current_object(), data=data)
46
47
 
47
- def verify_email_token(self, token, within=None):
48
+ def verify_email_with_token(self, token, within=None):
48
49
  """
49
50
  View function which handles an email verification request.
50
51
  This is always a GET from an email - so for 'spa' must always redirect.
@@ -63,7 +64,7 @@ class EmailVerification:
63
64
  return ("invalid_token", None)
64
65
 
65
66
  token_user_identity, token_email_hash = token_data
66
- user = _usercenter.get_user_by_identity(token_user_identity)
67
+ user = _userstore.get_user_by_identity(token_user_identity)
67
68
 
68
69
  if not user:
69
70
  return ("no_user", None)
@@ -78,6 +79,6 @@ class EmailVerification:
78
79
  user.email_verified_at = datetime.now()
79
80
  user.actived = True
80
81
 
81
- _usercenter.save_user(user)
82
+ _userstore.save_user(user)
82
83
 
83
84
  return ("verified", user)
@@ -0,0 +1,78 @@
1
+ from datetime import datetime
2
+ from flask import url_for
3
+ from flask import current_app
4
+ from ..proxies import _security
5
+ from ..proxies import _userstore
6
+ from ..signals import to_send_email
7
+
8
+
9
+ class ResetPassword:
10
+ """
11
+ Class to handle reset password functionality.
12
+ This class provides methods to generate reset token and reset password with the token.
13
+ """
14
+
15
+ def __init__(self, app=None):
16
+ self.app = app
17
+ self.serializer_name = "reset_password"
18
+
19
+ def generate_reset_password_token(self, user):
20
+ """Generates a reset password token for the specified user.
21
+ :param user: The user to work with
22
+ """
23
+ data = (str(user.id), _security.hasher.hash(user.email))
24
+ token = _security.serializer.dumps(self.serializer_name, data)
25
+ return token
26
+
27
+ def send_reset_password_token(self, user):
28
+ """Sends the reset password token.
29
+ :param user: The user to send the reset password token to.
30
+ """
31
+ if user is None or user.email is None:
32
+ return
33
+ token = self.generate_reset_password_token(user)
34
+ link = url_for("user.reset_password", token=token, _external=True)
35
+
36
+ data = {
37
+ "type": "reset_password",
38
+ "email": user.email,
39
+ "reset_password_link": link,
40
+ "reset_password_token": token,
41
+ "user": user,
42
+ }
43
+
44
+ to_send_email.send(current_app._get_current_object(), data=data)
45
+
46
+ def reset_password_with_token(self, token, password, within=None):
47
+ """
48
+ Resets the password for a user with the given token.
49
+ """
50
+ if within is None:
51
+ within = _security.get_within(self.serializer_name)
52
+
53
+ expired, invalid, token_data = _security.serializer.loads(
54
+ self.serializer_name, token, within
55
+ )
56
+
57
+ if expired:
58
+ return ("expired", None)
59
+
60
+ if invalid or not token_data:
61
+ return ("invalid_token", None)
62
+
63
+ token_user_identity, token_email_hash = token_data
64
+ user = _userstore.get_user_by_identity(token_user_identity)
65
+
66
+ if not user:
67
+ return ("no_user", None)
68
+
69
+ if not _security.hasher.verify(user.email, token_email_hash):
70
+ return ("invalid_token", None)
71
+
72
+ if not user.email_verified:
73
+ return ("email_not_verified", None)
74
+
75
+ _userstore.user_set(user, password=user.hash_password(password))
76
+ _userstore.save_user(user)
77
+
78
+ return ("ok", None)
@@ -22,7 +22,7 @@ class TimedUrlSerializer:
22
22
  return token
23
23
 
24
24
  def loads(
25
- self, serializer_name: str, token: str, max_age: int | None = None
25
+ self, serializer_name: str, token: str, max_age: int | None = 259200 # 3 days
26
26
  ) -> tuple[bool, bool, t.Any]:
27
27
  """Get the status of a token and return data.
28
28
 
@@ -42,7 +42,7 @@ class TimedUrlSerializer:
42
42
  try:
43
43
  data = s.loads(token, max_age=max_age)
44
44
  except SignatureExpired:
45
- _, data = s.loads_unsafe(token)
45
+ # _, data = s.loads_unsafe(token)
46
46
  expired = True
47
47
  except (BadSignature, TypeError, ValueError):
48
48
  invalid = True
@@ -0,0 +1,38 @@
1
+ import secrets
2
+ import string
3
+ import pyotp
4
+
5
+
6
+ class TwoFactorAuthentication:
7
+ def __init__(self, app=None):
8
+ self.app = app
9
+
10
+ def generate_totp_secret(self):
11
+ return pyotp.random_base32()
12
+
13
+ def generate_recovery_codes(self, count=10, length=16):
14
+ alphabet = string.ascii_letters + string.digits
15
+ codes = set()
16
+ while len(codes) < count:
17
+ code = "".join(secrets.choice(alphabet) for _ in range(length))
18
+ codes.add(code)
19
+ return list(codes)
20
+
21
+ def get_totp_code(self, otp_secret):
22
+ if otp_secret is None:
23
+ return None
24
+ otp = pyotp.TOTP(otp_secret)
25
+ return otp.now()
26
+
27
+ def get_totp_uri(self, otp_secret, username):
28
+ otp = pyotp.TOTP(otp_secret)
29
+ if self.app and self.app.config.get("APP_NAME"):
30
+ app_name = self.app.config.get("APP_NAME")
31
+ else:
32
+ app_name = "UnknownApp"
33
+ uri = otp.provisioning_uri(name=username, issuer_name=app_name)
34
+ return uri
35
+
36
+ def verify_totp(self, otp_secret, token):
37
+ otp = pyotp.TOTP(otp_secret)
38
+ return otp.verify(token)
@@ -3,6 +3,6 @@ $('#fa_modal_window').on('show.bs.modal', function (event) {
3
3
  let modal = $(this)
4
4
  modal.find('.modal-content').load(relatedTarget.attr('href'), function () {
5
5
  // window.faForm.applyGlobalStyles(document.getElementsByClassName('modal-content'));
6
- window.faForm.applyGlobalStyles(modal.find('.modal-content'), true);
6
+ // window.faForm.applyGlobalStyles(modal.find('.modal-content'), true);
7
7
  })
8
8
  })