fastapi-admin-kit 0.1.1__tar.gz → 0.2.0__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 (185) hide show
  1. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/PKG-INFO +19 -3
  2. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/README.md +16 -1
  3. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/__init__.py +3 -1
  4. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/actions/base.py +2 -6
  5. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/actions/registry.py +1 -3
  6. fastapi_admin_kit-0.2.0/fastapi_admin_kit/admin/admin_database.py +249 -0
  7. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin/admin_router.py +2 -0
  8. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin/admin_template.py +19 -30
  9. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin/builtin_models.py +70 -50
  10. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin/core.py +64 -71
  11. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin.py +54 -66
  12. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/api/auth.py +30 -43
  13. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/api/crud.py +9 -8
  14. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/api/deps.py +2 -6
  15. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/api/roles.py +8 -12
  16. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/api/search.py +3 -2
  17. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/context.py +1 -1
  18. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/diff.py +6 -6
  19. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/event_bus.py +1 -3
  20. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/models.py +1 -3
  21. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/__init__.py +15 -8
  22. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/backend.py +38 -20
  23. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/csrf.py +33 -11
  24. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/dependencies.py +3 -7
  25. fastapi_admin_kit-0.2.0/fastapi_admin_kit/auth/hasher.py +43 -0
  26. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/identity.py +9 -11
  27. fastapi_admin_kit-0.2.0/fastapi_admin_kit/auth/mixins.py +87 -0
  28. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/models.py +56 -65
  29. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/password.py +3 -1
  30. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/permissions.py +11 -10
  31. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/protocol.py +1 -1
  32. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/router.py +1 -1
  33. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/session.py +1 -3
  34. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/totp.py +10 -7
  35. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/views.py +68 -20
  36. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/cli/__init__.py +19 -15
  37. fastapi_admin_kit-0.2.0/fastapi_admin_kit/cli/helpers.py +103 -0
  38. fastapi_admin_kit-0.2.0/fastapi_admin_kit/cli/migrate.py +247 -0
  39. fastapi_admin_kit-0.2.0/fastapi_admin_kit/cli/permissions.py +184 -0
  40. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/cli/scaffold.py +10 -10
  41. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/cli/user.py +36 -56
  42. fastapi_admin_kit-0.2.0/fastapi_admin_kit/config/auth.py +99 -0
  43. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/config/behavior.py +2 -0
  44. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/config/nav.py +1 -3
  45. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/dashboard/__init__.py +6 -0
  46. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/filters/registry.py +3 -9
  47. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/flash.py +2 -0
  48. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/form/pipeline.py +13 -1
  49. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/inspection/__init__.py +29 -3
  50. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/inspection/registry.py +2 -6
  51. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/inspection.py +1 -3
  52. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/modeladmin.py +7 -20
  53. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/nav.py +6 -12
  54. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/registry/core.py +18 -14
  55. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/registry/validation.py +2 -6
  56. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/router.py +104 -55
  57. fastapi_admin_kit-0.2.0/fastapi_admin_kit/search_utils.py +71 -0
  58. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/static/css/admin.css +17 -18
  59. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/static/js/admin.js +71 -1
  60. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/base.html +3 -0
  61. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/macros/form_fields.html +1 -1
  62. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/macros/widgets.html +33 -33
  63. fastapi_admin_kit-0.2.0/fastapi_admin_kit/templates/pages/403.html +37 -0
  64. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/dashboard.html +31 -4
  65. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/form.html +1 -4
  66. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/login.html +14 -3
  67. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/role_form.html +2 -2
  68. fastapi_admin_kit-0.2.0/fastapi_admin_kit/templates/pages/search.html +120 -0
  69. fastapi_admin_kit-0.2.0/fastapi_admin_kit/templates/pages/users/form.html +249 -0
  70. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/_inline_edit_row.html +1 -1
  71. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/field_wrapper.html +1 -1
  72. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/head.html +1 -1
  73. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/head_minimal.html +1 -1
  74. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/list_table.html +1 -1
  75. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/mobile_backdrop.html +1 -1
  76. fastapi_admin_kit-0.2.0/fastapi_admin_kit/templates/partials/perm_select_widget.html +46 -0
  77. fastapi_admin_kit-0.2.0/fastapi_admin_kit/templates/partials/permission_widget.html +113 -0
  78. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/scripts.html +1 -1
  79. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/types.py +3 -0
  80. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/validation.py +2 -6
  81. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/audit.py +30 -24
  82. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/bulk.py +1 -3
  83. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/class_views.py +115 -147
  84. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/context.py +25 -80
  85. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/dashboard.py +63 -26
  86. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/delete.py +1 -3
  87. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/factory.py +37 -74
  88. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/form.py +11 -9
  89. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/profile.py +18 -17
  90. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/protocols.py +1 -3
  91. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/renderers.py +39 -66
  92. fastapi_admin_kit-0.2.0/fastapi_admin_kit/views/roles.py +310 -0
  93. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/search.py +1 -3
  94. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/settings.py +1 -0
  95. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/sidebar.py +21 -18
  96. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/totp.py +11 -17
  97. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/users.py +24 -32
  98. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views.py +15 -15
  99. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/widgets/inputs.py +2 -0
  100. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/widgets/registry.py +1 -3
  101. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/widgets/relation.py +11 -1
  102. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/pyproject.toml +3 -1
  103. fastapi_admin_kit-0.1.1/fastapi_admin_kit/admin/admin_database.py +0 -147
  104. fastapi_admin_kit-0.1.1/fastapi_admin_kit/config/auth.py +0 -54
  105. fastapi_admin_kit-0.1.1/fastapi_admin_kit/templates/pages/users/form.html +0 -229
  106. fastapi_admin_kit-0.1.1/fastapi_admin_kit/templates/partials/permission_widget.html +0 -86
  107. fastapi_admin_kit-0.1.1/fastapi_admin_kit/views/roles.py +0 -230
  108. fastapi_admin_kit-0.1.1/test_debug.db-journal +0 -0
  109. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/.gitignore +0 -0
  110. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/LICENSE +0 -0
  111. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/actions/__init__.py +0 -0
  112. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin/__init__.py +0 -0
  113. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin/admin_config.py +0 -0
  114. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin/decorators.py +0 -0
  115. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/admin/state.py +0 -0
  116. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/api/__init__.py +0 -0
  117. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/api/schema_generator.py +0 -0
  118. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/api/schemas.py +0 -0
  119. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/__init__.py +0 -0
  120. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/events.py +0 -0
  121. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/listener.py +0 -0
  122. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/logger.py +0 -0
  123. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/middleware.py +0 -0
  124. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/audit/sqlalchemy_logger.py +0 -0
  125. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/auth/ratelimit.py +0 -0
  126. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/cli/__main__.py +0 -0
  127. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/config/__init__.py +0 -0
  128. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/config/audit.py +0 -0
  129. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/config/database.py +0 -0
  130. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/config/storage.py +0 -0
  131. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/config/theme.py +0 -0
  132. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/config/ui.py +0 -0
  133. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/db.py +0 -0
  134. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/exceptions.py +0 -0
  135. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/field_types.py +0 -0
  136. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/filters/__init__.py +0 -0
  137. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/filters/base.py +0 -0
  138. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/form/__init__.py +0 -0
  139. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/models/__init__.py +0 -0
  140. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/models/base.py +0 -0
  141. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/pagination/__init__.py +0 -0
  142. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/pagination/base.py +0 -0
  143. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/pagination/cursor.py +0 -0
  144. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/pagination/dynamic.py +0 -0
  145. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/pagination/offset.py +0 -0
  146. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/plugins/__init__.py +0 -0
  147. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/py.typed +0 -0
  148. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/registry/__init__.py +0 -0
  149. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/registry.py +0 -0
  150. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/static/css/presets.css +0 -0
  151. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/static/css/tokens.css +0 -0
  152. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/static/css/variables.css +0 -0
  153. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/static/icons/heroicons.svg +0 -0
  154. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/static/js/htmx-config.js +0 -0
  155. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/storage/__init__.py +0 -0
  156. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/storage/base.py +0 -0
  157. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/storage/local.py +0 -0
  158. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/macros/icons.html +0 -0
  159. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/macros/table.html +0 -0
  160. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/2fa/setup.html +0 -0
  161. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/2fa/verify.html +0 -0
  162. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/audit_detail.html +0 -0
  163. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/audit_log.html +0 -0
  164. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/detail.html +0 -0
  165. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/list.html +0 -0
  166. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/profile/password.html +0 -0
  167. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/profile/profile.html +0 -0
  168. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/roles/form.html +0 -0
  169. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/roles/list.html +0 -0
  170. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/roles.html +0 -0
  171. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/settings/theme.html +0 -0
  172. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/pages/users/list.html +0 -0
  173. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/command_palette.html +0 -0
  174. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/flash_messages.html +0 -0
  175. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/inline_edit_form.html +0 -0
  176. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/pagination.html +0 -0
  177. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/sidebar.html +0 -0
  178. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/templates/partials/topbar.html +0 -0
  179. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/__init__.py +0 -0
  180. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/extra.py +0 -0
  181. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/views/list.py +0 -0
  182. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/widgets/__init__.py +0 -0
  183. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/widgets/base.py +0 -0
  184. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/fastapi_admin_kit/widgets/resolver.py +0 -0
  185. {fastapi_admin_kit-0.1.1 → fastapi_admin_kit-0.2.0}/mkdocs.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-admin-kit
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Drop-in admin panel for FastAPI + SQLAlchemy + SQLModel apps
5
5
  Project-URL: Homepage, https://github.com/borhanst/fastapi-admin-kit
6
6
  Project-URL: Documentation, https://borhanst.github.io/fastapi-admin-kit/
@@ -25,7 +25,7 @@ Classifier: Typing :: Typed
25
25
  Requires-Python: >=3.11
26
26
  Requires-Dist: aiosqlite>=0.20.0
27
27
  Requires-Dist: bcrypt>=4.0.0
28
- Requires-Dist: fastapi>=0.115.0
28
+ Requires-Dist: fastapi>=0.100.0
29
29
  Requires-Dist: itsdangerous>=2.2.0
30
30
  Requires-Dist: jinja2>=3.1.0
31
31
  Requires-Dist: pyjwt>=2.13.0
@@ -35,6 +35,7 @@ Requires-Dist: uvicorn>=0.30.0
35
35
  Provides-Extra: dev
36
36
  Requires-Dist: hatch>=1.13.0; extra == 'dev'
37
37
  Requires-Dist: httpx>=0.27.0; extra == 'dev'
38
+ Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
38
39
  Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
39
40
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
40
41
  Requires-Dist: ruff>=0.5.0; extra == 'dev'
@@ -92,16 +93,29 @@ from contextlib import asynccontextmanager
92
93
  from fastapi import FastAPI
93
94
  from sqlalchemy import Column, Float, Integer, String
94
95
  from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
95
- from sqlalchemy.orm import DeclarativeBase, sessionmaker
96
+ from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker
96
97
 
97
98
  from fastapi_admin_kit import Admin
98
99
  from fastapi_admin_kit.auth.backend import BuiltinAuthBackend
100
+ from fastapi_admin_kit.auth.mixins import AuthModelMixin
101
+ from fastapi_admin_kit.auth.models import Role, admin_user_roles
99
102
 
100
103
 
101
104
  class Base(DeclarativeBase):
102
105
  pass
103
106
 
104
107
 
108
+ class User(AuthModelMixin, Base):
109
+ __tablename__ = "users"
110
+ id = Column(Integer, primary_key=True)
111
+ email = Column(String(255), unique=True, nullable=False)
112
+ full_name = Column(String(255))
113
+
114
+ roles = relationship(
115
+ "Role", secondary=admin_user_roles, back_populates="users"
116
+ )
117
+
118
+
105
119
  class Product(Base):
106
120
  __tablename__ = "products"
107
121
  id = Column(Integer, primary_key=True)
@@ -120,6 +134,7 @@ async_session = sessionmaker(engine, class_=AsyncSession)
120
134
  async def lifespan(app: FastAPI):
121
135
  async with engine.begin() as conn:
122
136
  await conn.run_sync(Base.metadata.create_all)
137
+ await admin.setup()
123
138
  yield
124
139
  await engine.dispose()
125
140
 
@@ -130,6 +145,7 @@ admin = Admin(
130
145
  engine=engine,
131
146
  base=Base,
132
147
  secret_key=SECRET_KEY,
148
+ auth_model=User,
133
149
  auth_backend=BuiltinAuthBackend(),
134
150
  )
135
151
  admin.register(Product)
@@ -40,16 +40,29 @@ from contextlib import asynccontextmanager
40
40
  from fastapi import FastAPI
41
41
  from sqlalchemy import Column, Float, Integer, String
42
42
  from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
43
- from sqlalchemy.orm import DeclarativeBase, sessionmaker
43
+ from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker
44
44
 
45
45
  from fastapi_admin_kit import Admin
46
46
  from fastapi_admin_kit.auth.backend import BuiltinAuthBackend
47
+ from fastapi_admin_kit.auth.mixins import AuthModelMixin
48
+ from fastapi_admin_kit.auth.models import Role, admin_user_roles
47
49
 
48
50
 
49
51
  class Base(DeclarativeBase):
50
52
  pass
51
53
 
52
54
 
55
+ class User(AuthModelMixin, Base):
56
+ __tablename__ = "users"
57
+ id = Column(Integer, primary_key=True)
58
+ email = Column(String(255), unique=True, nullable=False)
59
+ full_name = Column(String(255))
60
+
61
+ roles = relationship(
62
+ "Role", secondary=admin_user_roles, back_populates="users"
63
+ )
64
+
65
+
53
66
  class Product(Base):
54
67
  __tablename__ = "products"
55
68
  id = Column(Integer, primary_key=True)
@@ -68,6 +81,7 @@ async_session = sessionmaker(engine, class_=AsyncSession)
68
81
  async def lifespan(app: FastAPI):
69
82
  async with engine.begin() as conn:
70
83
  await conn.run_sync(Base.metadata.create_all)
84
+ await admin.setup()
71
85
  yield
72
86
  await engine.dispose()
73
87
 
@@ -78,6 +92,7 @@ admin = Admin(
78
92
  engine=engine,
79
93
  base=Base,
80
94
  secret_key=SECRET_KEY,
95
+ auth_model=User,
81
96
  auth_backend=BuiltinAuthBackend(),
82
97
  )
83
98
  admin.register(Product)
@@ -2,6 +2,7 @@
2
2
 
3
3
  from fastapi_admin_kit.admin import Admin
4
4
  from fastapi_admin_kit.admin.decorators import column
5
+ from fastapi_admin_kit.auth.mixins import AuthModelMixin
5
6
  from fastapi_admin_kit.config import DatabaseConfig, DatabaseType
6
7
  from fastapi_admin_kit.exceptions import ConfigError
7
8
  from fastapi_admin_kit.nav import (
@@ -40,6 +41,7 @@ from fastapi_admin_kit.views import (
40
41
  __all__ = [
41
42
  "Admin",
42
43
  "AdminRegistry",
44
+ "AuthModelMixin",
43
45
  "ConfigError",
44
46
  "DatabaseConfig",
45
47
  "DatabaseType",
@@ -73,4 +75,4 @@ __all__ = [
73
75
  # Per-model assets
74
76
  "AdminExtra",
75
77
  ]
76
- __version__ = "0.1.1"
78
+ __version__ = "0.2.0"
@@ -36,9 +36,7 @@ class Action(ABC):
36
36
  self.description = description
37
37
 
38
38
  @abstractmethod
39
- async def execute(
40
- self, objects: list[Any], request: Request | None
41
- ) -> None:
39
+ async def execute(self, objects: list[Any], request: Request | None) -> None:
42
40
  """Run the action against the selected objects."""
43
41
  ...
44
42
 
@@ -58,9 +56,7 @@ class Action(ABC):
58
56
  class ModelAction(Action):
59
57
  """Action that operates on a single model instance (row/detail actions)."""
60
58
 
61
- async def execute_single(
62
- self, obj: Any, request: Request | None
63
- ) -> None:
59
+ async def execute_single(self, obj: Any, request: Request | None) -> None:
64
60
  """Run the action on a single object.
65
61
 
66
62
  Override this instead of execute for single-object actions.
@@ -19,9 +19,7 @@ class DeleteSelectedAction(Action):
19
19
  confirmation_message="Are you sure you want to delete the selected items?",
20
20
  )
21
21
 
22
- async def execute(
23
- self, objects: list[Any], request: Request | None
24
- ) -> None:
22
+ async def execute(self, objects: list[Any], request: Request | None) -> None:
25
23
  # Actual deletion is handled by the existing bulk handler in views/factory.py
26
24
  pass
27
25
 
@@ -0,0 +1,249 @@
1
+ """Admin database setup and initialization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from fastapi_admin_kit.config.database import DatabaseConfig
8
+
9
+
10
+ class AdminDatabase:
11
+ """Handles database setup, table creation, and role seeding."""
12
+
13
+ def __init__(
14
+ self,
15
+ engine: Any | None = None,
16
+ base: Any | None = None,
17
+ database_config: DatabaseConfig | None = None,
18
+ ):
19
+ self.engine = engine
20
+ self.base = base
21
+ self.database_config = database_config
22
+
23
+ def _ensure_engine(self) -> Any:
24
+ """
25
+ Create the async engine from ``database_config`` if no engine is set.
26
+ """
27
+ if self.engine is None and self.database_config is not None:
28
+ self.engine = self.database_config.create_engine()
29
+ return self.engine
30
+
31
+ async def _create_tables(self) -> None:
32
+ """Create all admin database tables (async-safe)."""
33
+ from sqlalchemy.ext.asyncio import AsyncEngine
34
+
35
+ from fastapi_admin_kit.audit import (
36
+ models as _audit_models, # noqa: F401
37
+ )
38
+
39
+ # Import models to register them with metadata
40
+ from fastapi_admin_kit.auth import models as _auth_models # noqa: F401
41
+ from fastapi_admin_kit.models.base import Base as AdminBase
42
+
43
+ if isinstance(self.engine, AsyncEngine):
44
+ # Async engine - use run_sync
45
+ async with self.engine.begin() as conn:
46
+ # Create admin tables
47
+ await conn.run_sync(AdminBase.metadata.create_all)
48
+ # Create user tables if Base is provided
49
+ if self.base is not None:
50
+ await conn.run_sync(self.base.metadata.create_all)
51
+ # Auto-migrate: add missing columns
52
+ await conn.run_sync(self._auto_migrate, AdminBase.metadata)
53
+ if self.base is not None:
54
+ await conn.run_sync(self._auto_migrate, self.base.metadata)
55
+ else:
56
+ # Sync engine - direct call
57
+ AdminBase.metadata.create_all(bind=self.engine)
58
+ if self.base is not None:
59
+ self.base.metadata.create_all(bind=self.engine)
60
+ # Auto-migrate: add missing columns
61
+ self._auto_migrate_sync(AdminBase.metadata)
62
+ if self.base is not None:
63
+ self._auto_migrate_sync(self.base.metadata)
64
+
65
+ def _auto_migrate_sync(self, metadata: Any) -> None:
66
+ """Sync version of auto-migrate."""
67
+ from sqlalchemy import inspect as sa_inspect
68
+ from sqlalchemy import text
69
+
70
+ inspector = sa_inspect(self.engine)
71
+ for table_name, table in metadata.tables.items():
72
+ if not inspector.has_table(table_name):
73
+ continue
74
+ existing_cols = {c["name"] for c in inspector.get_columns(table_name)}
75
+ for col in table.columns:
76
+ if col.name not in existing_cols:
77
+ col_type = col.type.compile(self.engine.dialect)
78
+ nullable = "NULL" if col.nullable else "NOT NULL"
79
+ default = ""
80
+ if col.server_default is not None:
81
+ default_sql = col.server_default.arg
82
+ if hasattr(default_sql, "text"):
83
+ default_sql = default_sql.text
84
+ default = f" DEFAULT {default_sql}"
85
+ elif col.default is not None and col.default.is_seq:
86
+ pass
87
+ sql = text(
88
+ f"""ALTER TABLE {table_name}
89
+ ADD COLUMN {col.name} {col_type}
90
+ {nullable}{default}"""
91
+ )
92
+ with self.engine.begin() as conn:
93
+ conn.execute(sql)
94
+
95
+ def _auto_migrate(self, sync_conn: Any, metadata: Any) -> None:
96
+ """
97
+ Add missing columns to existing tables (sync, called via run_sync).
98
+ """
99
+ from sqlalchemy import inspect as sa_inspect
100
+ from sqlalchemy import text
101
+
102
+ dialect = sync_conn.dialect if hasattr(sync_conn, "dialect") else None
103
+ if dialect is None:
104
+ return
105
+
106
+ inspector = sa_inspect(sync_conn)
107
+ for table_name, table in metadata.tables.items():
108
+ if not inspector.has_table(table_name):
109
+ continue
110
+ existing_cols = {c["name"] for c in inspector.get_columns(table_name)}
111
+ for col in table.columns:
112
+ if col.name not in existing_cols:
113
+ col_type = col.type.compile(dialect)
114
+ nullable = "NULL" if col.nullable else "NOT NULL"
115
+ default = ""
116
+ if col.server_default is not None:
117
+ default_sql = col.server_default.arg
118
+ if hasattr(default_sql, "text"):
119
+ default_sql = default_sql.text
120
+ default = f" DEFAULT {default_sql}"
121
+ elif not col.nullable:
122
+ # SQLite requires a default for NOT NULL columns being added
123
+ type_defaults = {
124
+ "VARCHAR": "''", "TEXT": "''", "INTEGER": "0",
125
+ "FLOAT": "0.0", "BOOLEAN": "0", "DATETIME": "''",
126
+ }
127
+ sql_type = col_type.upper().split("(")[0]
128
+ temp_val = type_defaults.get(sql_type, "''")
129
+ default = f" DEFAULT {temp_val}"
130
+ sql = text(
131
+ f"""ALTER TABLE {table_name}
132
+ ADD COLUMN
133
+ {col.name} {col_type} {nullable}{default}
134
+ """
135
+ )
136
+ sync_conn.execute(sql)
137
+
138
+ async def _seed_roles(self, seed_roles: list, seed_roles_overwrite: bool = False) -> None:
139
+ """Seed default roles if none exist (or if overwrite is enabled)."""
140
+ from sqlalchemy import select
141
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
142
+ from sqlalchemy.orm import Session, sessionmaker
143
+
144
+ from fastapi_admin_kit.auth.models import Permission, Role
145
+
146
+ is_async = isinstance(self.engine, AsyncEngine)
147
+
148
+ if is_async:
149
+ # Use AsyncSession for async engine
150
+ session_local = sessionmaker(self.engine, class_=AsyncSession, expire_on_commit=False)
151
+ async with session_local() as session:
152
+ # Check existing count
153
+ result = await session.execute(select(Role))
154
+ existing_count = len(result.scalars().all())
155
+
156
+ if existing_count > 0 and not seed_roles_overwrite:
157
+ return
158
+
159
+ if seed_roles_overwrite:
160
+ await session.execute(select(Role).delete())
161
+
162
+ for role_spec in seed_roles:
163
+ role = Role(name=role_spec.name, description=role_spec.description)
164
+ session.add(role)
165
+ await session.flush() # get role.id
166
+ # Eagerly load M2M relationship for async session
167
+ await session.refresh(role, ["permissions"])
168
+
169
+ if role_spec.permissions:
170
+ for table_name, perms in role_spec.permissions.items():
171
+ # Find or create permission for this table
172
+ from sqlalchemy import select as sa_select
173
+
174
+ result = await session.execute(
175
+ sa_select(Permission).filter_by(table_name=table_name)
176
+ )
177
+ existing = result.scalar_one_or_none()
178
+ if existing is None:
179
+ perm = Permission(
180
+ name=table_name,
181
+ table_name=table_name,
182
+ can_view=perms.get("view", False),
183
+ can_create=perms.get("create", False),
184
+ can_edit=perms.get("edit", False),
185
+ can_delete=perms.get("delete", False),
186
+ )
187
+ session.add(perm)
188
+ await session.flush()
189
+ else:
190
+ perm = existing
191
+ # Link permission to role via M2M
192
+ role.permissions.append(perm)
193
+
194
+ await session.commit()
195
+ else:
196
+ # Use sync Session for sync engine
197
+ session = Session(bind=self.engine)
198
+ try:
199
+ existing_count = session.query(Role).count()
200
+
201
+ if existing_count > 0 and not seed_roles_overwrite:
202
+ return
203
+
204
+ if seed_roles_overwrite:
205
+ session.query(Role).delete()
206
+
207
+ for role_spec in seed_roles:
208
+ role = Role(name=role_spec.name, description=role_spec.description)
209
+ session.add(role)
210
+ session.flush() # get role.id
211
+
212
+ if role_spec.permissions:
213
+ for table_name, perms in role_spec.permissions.items():
214
+ # Find or create permission for this table
215
+ existing = (
216
+ session.query(Permission).filter_by(table_name=table_name).first()
217
+ )
218
+ if existing is None:
219
+ perm = Permission(
220
+ name=table_name,
221
+ table_name=table_name,
222
+ can_view=perms.get("view", False),
223
+ can_create=perms.get("create", False),
224
+ can_edit=perms.get("edit", False),
225
+ can_delete=perms.get("delete", False),
226
+ )
227
+ session.add(perm)
228
+ session.flush()
229
+ else:
230
+ perm = existing
231
+ # Link permission to role via M2M
232
+ role.permissions.append(perm)
233
+
234
+ session.commit()
235
+ finally:
236
+ session.close()
237
+
238
+ def _init_session_backend(
239
+ self, secret_key: str, session_ttl: int, cookie_name: str, secure: bool
240
+ ) -> Any:
241
+ """Create and store the signed-cookie session backend."""
242
+ from fastapi_admin_kit.auth.session import SignedCookieSessionBackend
243
+
244
+ return SignedCookieSessionBackend(
245
+ secret_key=secret_key,
246
+ session_ttl=session_ttl,
247
+ cookie_name=cookie_name,
248
+ secure=secure,
249
+ )
@@ -29,6 +29,8 @@ class AdminRouter:
29
29
  registry = AdminRegistry()
30
30
 
31
31
  for registered in registry.all():
32
+ if getattr(registered.admin, "skip_auto_routes", False):
33
+ continue
32
34
  model_router = build_model_router(registered)
33
35
  app.include_router(model_router, prefix=self.admin_path)
34
36
 
@@ -84,48 +84,41 @@ class AdminTemplate:
84
84
 
85
85
  if user and not is_superuser:
86
86
  role_ids = (
87
- snapshot.get("role_ids", [])
88
- if snapshot
89
- else getattr(user, "role_ids", [])
87
+ snapshot.get("role_ids", []) if snapshot else getattr(user, "role_ids", [])
90
88
  )
91
89
  if role_ids:
92
90
  try:
93
91
  from sqlalchemy import select
94
92
  from sqlalchemy.orm import Session
95
93
 
96
- from fastapi_admin_kit.auth.models import AdminPermission
94
+ from fastapi_admin_kit.auth.models import Permission, admin_role_permissions
97
95
 
98
96
  engine = request.app.state.admin_engine
99
97
  with Session(engine) as s:
100
98
  result = s.execute(
101
- select(AdminPermission).filter(
102
- AdminPermission.role_id.in_(role_ids)
99
+ select(Permission)
100
+ .join(
101
+ admin_role_permissions,
102
+ Permission.id == admin_role_permissions.c.permission_id,
103
103
  )
104
+ .filter(admin_role_permissions.c.role_id.in_(role_ids))
104
105
  )
105
106
  rows = result.scalars().all()
106
107
  for perm in rows:
107
108
  if perm.table_name in permissions_map:
108
109
  existing = permissions_map[perm.table_name]
109
- permissions_map[perm.table_name] = (
110
- PermissionSet(
111
- can_view=existing.can_view
112
- or perm.can_view,
113
- can_create=existing.can_create
114
- or perm.can_create,
115
- can_edit=existing.can_edit
116
- or perm.can_edit,
117
- can_delete=existing.can_delete
118
- or perm.can_delete,
119
- )
110
+ permissions_map[perm.table_name] = PermissionSet(
111
+ can_view=existing.can_view or perm.can_view,
112
+ can_create=existing.can_create or perm.can_create,
113
+ can_edit=existing.can_edit or perm.can_edit,
114
+ can_delete=existing.can_delete or perm.can_delete,
120
115
  )
121
116
  else:
122
- permissions_map[perm.table_name] = (
123
- PermissionSet(
124
- can_view=perm.can_view,
125
- can_create=perm.can_create,
126
- can_edit=perm.can_edit,
127
- can_delete=perm.can_delete,
128
- )
117
+ permissions_map[perm.table_name] = PermissionSet(
118
+ can_view=perm.can_view,
119
+ can_create=perm.can_create,
120
+ can_edit=perm.can_edit,
121
+ can_delete=perm.can_delete,
129
122
  )
130
123
  except Exception:
131
124
  pass
@@ -147,9 +140,7 @@ class AdminTemplate:
147
140
  for item in items:
148
141
  if not _item_visible(item):
149
142
  continue
150
- filtered_children = (
151
- _filter_items(item.children) if item.children else []
152
- )
143
+ filtered_children = _filter_items(item.children) if item.children else []
153
144
  result.append(replace(item, children=filtered_children))
154
145
  return result
155
146
 
@@ -195,9 +186,7 @@ class AdminTemplate:
195
186
  "settings_visible": settings_visible,
196
187
  }
197
188
 
198
- def apply_sidebar_context(
199
- self, request: Any, user: Any, context: dict
200
- ) -> dict:
189
+ def apply_sidebar_context(self, request: Any, user: Any, context: dict) -> dict:
201
190
  """Inject nav_groups + permissions_map into a template context dict."""
202
191
  context.update(self.build_sidebar_context(request, user=user))
203
192
  return context