oxutils 0.1.22__tar.gz → 0.3.2__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 (219) hide show
  1. {oxutils-0.1.22 → oxutils-0.3.2}/PKG-INFO +6 -3
  2. {oxutils-0.1.22 → oxutils-0.3.2}/pyproject.toml +14 -4
  3. oxutils-0.3.2/src/oxutils/__init__.py +28 -0
  4. oxutils-0.3.2/src/oxutils/auth/adapter.py +212 -0
  5. oxutils-0.3.2/src/oxutils/auth/app_settings.py +25 -0
  6. oxutils-0.3.2/src/oxutils/auth/apps.py +6 -0
  7. oxutils-0.3.2/src/oxutils/auth/constants.py +14 -0
  8. oxutils-0.3.2/src/oxutils/auth/controllers.py +203 -0
  9. oxutils-0.3.2/src/oxutils/auth/emails/controllers.py +167 -0
  10. oxutils-0.3.2/src/oxutils/auth/emails/schemas.py +82 -0
  11. oxutils-0.3.2/src/oxutils/auth/emails/services.py +51 -0
  12. oxutils-0.3.2/src/oxutils/auth/exceptions.py +29 -0
  13. oxutils-0.3.2/src/oxutils/auth/invitations/backend.py +229 -0
  14. oxutils-0.3.2/src/oxutils/auth/invitations/controllers.py +248 -0
  15. oxutils-0.3.2/src/oxutils/auth/invitations/models.py +134 -0
  16. oxutils-0.3.2/src/oxutils/auth/invitations/schemas.py +92 -0
  17. oxutils-0.3.2/src/oxutils/auth/invitations/tokens.py +34 -0
  18. oxutils-0.3.2/src/oxutils/auth/mfa/__init__.py +23 -0
  19. oxutils-0.3.2/src/oxutils/auth/mfa/adapter.py +5 -0
  20. oxutils-0.3.2/src/oxutils/auth/mfa/base/controllers.py +67 -0
  21. oxutils-0.3.2/src/oxutils/auth/mfa/base/schemas.py +66 -0
  22. oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes/controllers.py +40 -0
  23. oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes/flows.py +76 -0
  24. oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes/schemas.py +28 -0
  25. oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes/services.py +80 -0
  26. oxutils-0.3.2/src/oxutils/auth/mfa/totp/controllers.py +50 -0
  27. oxutils-0.3.2/src/oxutils/auth/mfa/totp/flows.py +35 -0
  28. oxutils-0.3.2/src/oxutils/auth/mfa/totp/permissions.py +23 -0
  29. oxutils-0.3.2/src/oxutils/auth/mfa/totp/schemas.py +51 -0
  30. oxutils-0.3.2/src/oxutils/auth/mfa/totp/services.py +135 -0
  31. oxutils-0.3.2/src/oxutils/auth/mfa/utils.py +29 -0
  32. oxutils-0.3.2/src/oxutils/auth/migrations/0001_initial.py +97 -0
  33. oxutils-0.3.2/src/oxutils/auth/mixins.py +68 -0
  34. oxutils-0.3.2/src/oxutils/auth/models.py +24 -0
  35. oxutils-0.3.2/src/oxutils/auth/password_reset/controllers.py +167 -0
  36. oxutils-0.3.2/src/oxutils/auth/password_reset/models.py +12 -0
  37. oxutils-0.3.2/src/oxutils/auth/password_reset/permissions.py +31 -0
  38. oxutils-0.3.2/src/oxutils/auth/password_reset/schemas.py +83 -0
  39. oxutils-0.3.2/src/oxutils/auth/registration/controllers.py +92 -0
  40. oxutils-0.3.2/src/oxutils/auth/registration/schemas.py +173 -0
  41. oxutils-0.3.2/src/oxutils/auth/schemas.py +315 -0
  42. oxutils-0.3.2/src/oxutils/auth/sessions/controllers.py +134 -0
  43. oxutils-0.3.2/src/oxutils/auth/sessions/schemas.py +18 -0
  44. oxutils-0.3.2/src/oxutils/auth/signals.py +91 -0
  45. oxutils-0.3.2/src/oxutils/auth/templatetags/spa_urls.py +17 -0
  46. oxutils-0.3.2/src/oxutils/auth/tokens/app_settings.py +6 -0
  47. oxutils-0.3.2/src/oxutils/auth/tokens/models.py +159 -0
  48. oxutils-0.3.2/src/oxutils/auth/tokens/schemas.py +26 -0
  49. oxutils-0.3.2/src/oxutils/auth/tokens/tokens.py +104 -0
  50. oxutils-0.3.2/src/oxutils/auth/tokens/utils.py +53 -0
  51. oxutils-0.3.2/src/oxutils/auth/utils.py +381 -0
  52. oxutils-0.3.2/src/oxutils/currency/models.py +109 -0
  53. oxutils-0.3.2/src/oxutils/currency/signals.py +25 -0
  54. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/enums/__init__.py +4 -0
  55. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/auth.py +28 -42
  56. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/middleware.py +72 -78
  57. oxutils-0.3.2/src/oxutils/models/__init__.py +31 -0
  58. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/models/base.py +31 -38
  59. oxutils-0.3.2/src/oxutils/models/change_tracker.py +64 -0
  60. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/models/invoice.py +168 -216
  61. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/authorization.py +4 -9
  62. oxutils-0.3.2/src/oxutils/oxiliere/enums.py +10 -0
  63. oxutils-0.3.2/src/oxutils/oxiliere/management/__init__.py +0 -0
  64. oxutils-0.3.2/src/oxutils/oxiliere/management/commands/__init__.py +0 -0
  65. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/management/commands/grant_tenant_owners.py +3 -2
  66. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/middleware.py +121 -56
  67. oxutils-0.3.2/src/oxutils/oxiliere/migrations/__init__.py +0 -0
  68. oxutils-0.3.2/src/oxutils/oxiliere/models.py +265 -0
  69. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/services.py +64 -25
  70. oxutils-0.3.2/src/oxutils/oxiliere/signals.py +106 -0
  71. oxutils-0.3.2/src/oxutils/pagination/__init__.py +0 -0
  72. oxutils-0.3.2/src/oxutils/permissions/__init__.py +0 -0
  73. oxutils-0.3.2/src/oxutils/permissions/constants.py +0 -0
  74. oxutils-0.3.2/src/oxutils/permissions/management/__init__.py +0 -0
  75. oxutils-0.3.2/src/oxutils/permissions/management/commands/__init__.py +0 -0
  76. oxutils-0.3.2/src/oxutils/permissions/migrations/__init__.py +0 -0
  77. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/models.py +21 -25
  78. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/utils.py +194 -227
  79. oxutils-0.3.2/src/oxutils/py.typed +0 -0
  80. oxutils-0.3.2/src/oxutils/s3.py +110 -0
  81. oxutils-0.3.2/src/oxutils/templates/account/email/base_message.txt +7 -0
  82. oxutils-0.3.2/src/oxutils/templates/email/password/reset_email_message.html +97 -0
  83. oxutils-0.3.2/src/oxutils/templates/email/password/reset_email_message.txt +14 -0
  84. oxutils-0.3.2/src/oxutils/templates/email/password/reset_email_subject.txt +2 -0
  85. oxutils-0.3.2/src/oxutils/templates/email/signup/email_message.html +88 -0
  86. oxutils-0.3.2/src/oxutils/templates/email/signup/email_subject.txt +2 -0
  87. oxutils-0.3.2/src/oxutils/templates/verified.html +81 -0
  88. oxutils-0.3.2/src/oxutils/users/__init__.py +0 -0
  89. oxutils-0.3.2/src/oxutils/users/admin.py +3 -0
  90. oxutils-0.3.2/src/oxutils/users/migrations/__init__.py +0 -0
  91. oxutils-0.1.22/src/oxutils/__init__.py +0 -23
  92. oxutils-0.1.22/src/oxutils/currency/models.py +0 -100
  93. oxutils-0.1.22/src/oxutils/models/__init__.py +0 -3
  94. oxutils-0.1.22/src/oxutils/oxiliere/enums.py +0 -10
  95. oxutils-0.1.22/src/oxutils/oxiliere/models.py +0 -192
  96. oxutils-0.1.22/src/oxutils/oxiliere/signals.py +0 -5
  97. {oxutils-0.1.22 → oxutils-0.3.2}/README.md +0 -0
  98. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/apps.py +0 -0
  99. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/__init__.py +0 -0
  100. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/apps.py +0 -0
  101. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/export.py +0 -0
  102. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/locale/fr/LC_MESSAGES/django.mo +0 -0
  103. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/locale/fr/LC_MESSAGES/django.po +0 -0
  104. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/masks.py +0 -0
  105. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/migrations/0001_initial.py +0 -0
  106. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/migrations/__init__.py +0 -0
  107. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/models.py +0 -0
  108. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/settings.py +0 -0
  109. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/utils.py +0 -0
  110. {oxutils-0.1.22/src/oxutils/context → oxutils-0.3.2/src/oxutils/auth}/__init__.py +0 -0
  111. {oxutils-0.1.22/src/oxutils/oxiliere → oxutils-0.3.2/src/oxutils/auth}/admin.py +0 -0
  112. {oxutils-0.1.22/src/oxutils/currency → oxutils-0.3.2/src/oxutils/auth/emails}/__init__.py +0 -0
  113. {oxutils-0.1.22/src/oxutils/currency/management → oxutils-0.3.2/src/oxutils/auth/invitations}/__init__.py +0 -0
  114. {oxutils-0.1.22/src/oxutils/currency/management/commands → oxutils-0.3.2/src/oxutils/auth/mfa/base}/__init__.py +0 -0
  115. {oxutils-0.1.22/src/oxutils/currency/migrations → oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes}/__init__.py +0 -0
  116. /oxutils-0.1.22/src/oxutils/jwt/__init__.py → /oxutils-0.3.2/src/oxutils/auth/mfa/totp/__ini__.py +0 -0
  117. {oxutils-0.1.22/src/oxutils/mixins → oxutils-0.3.2/src/oxutils/auth/migrations}/__init__.py +0 -0
  118. {oxutils-0.1.22/src/oxutils/oxiliere → oxutils-0.3.2/src/oxutils/auth/password_reset}/__init__.py +0 -0
  119. {oxutils-0.1.22/src/oxutils/oxiliere/management → oxutils-0.3.2/src/oxutils/auth/registration}/__init__.py +0 -0
  120. {oxutils-0.1.22/src/oxutils/oxiliere/management/commands → oxutils-0.3.2/src/oxutils/auth/sessions}/__init__.py +0 -0
  121. {oxutils-0.1.22/src/oxutils/oxiliere/migrations → oxutils-0.3.2/src/oxutils/auth/templatetags}/__init__.py +0 -0
  122. {oxutils-0.1.22/src/oxutils/pagination → oxutils-0.3.2/src/oxutils/auth/tokens}/__init__.py +0 -0
  123. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/celery/__init__.py +0 -0
  124. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/celery/base.py +0 -0
  125. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/celery/settings.py +0 -0
  126. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/conf.py +0 -0
  127. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/constants.py +0 -0
  128. {oxutils-0.1.22/src/oxutils/permissions → oxutils-0.3.2/src/oxutils/context}/__init__.py +0 -0
  129. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/context/site_name_processor.py +0 -0
  130. {oxutils-0.1.22/src/oxutils/permissions/management → oxutils-0.3.2/src/oxutils/currency}/__init__.py +0 -0
  131. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/admin.py +0 -0
  132. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/apps.py +0 -0
  133. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/caches.py +0 -0
  134. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/controllers.py +0 -0
  135. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/enums.py +0 -0
  136. {oxutils-0.1.22/src/oxutils/permissions/management/commands → oxutils-0.3.2/src/oxutils/currency/management}/__init__.py +0 -0
  137. {oxutils-0.1.22/src/oxutils/permissions/migrations → oxutils-0.3.2/src/oxutils/currency/management/commands}/__init__.py +0 -0
  138. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/management/commands/sync_currency.py +0 -0
  139. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/migrations/0001_initial.py +0 -0
  140. {oxutils-0.1.22/src/oxutils/users → oxutils-0.3.2/src/oxutils/currency/migrations}/__init__.py +0 -0
  141. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/schemas.py +0 -0
  142. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/tests.py +0 -0
  143. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/utils.py +0 -0
  144. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/enums/audit.py +0 -0
  145. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/enums/invoices.py +0 -0
  146. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/exceptions.py +0 -0
  147. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/functions.py +0 -0
  148. {oxutils-0.1.22/src/oxutils/users/migrations → oxutils-0.3.2/src/oxutils/jwt}/__init__.py +0 -0
  149. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/models.py +0 -0
  150. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/tokens.py +0 -0
  151. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/utils.py +0 -0
  152. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/logger/__init__.py +0 -0
  153. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/logger/receivers.py +0 -0
  154. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/logger/settings.py +0 -0
  155. /oxutils-0.1.22/src/oxutils/permissions/constants.py → /oxutils-0.3.2/src/oxutils/mixins/__init__.py +0 -0
  156. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/mixins/base.py +0 -0
  157. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/mixins/schemas.py +0 -0
  158. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/mixins/services.py +0 -0
  159. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/models/billing.py +0 -0
  160. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/models/fields.py +0 -0
  161. /oxutils-0.1.22/src/oxutils/py.typed → /oxutils-0.3.2/src/oxutils/oxiliere/__init__.py +0 -0
  162. {oxutils-0.1.22/src/oxutils/permissions → oxutils-0.3.2/src/oxutils/oxiliere}/admin.py +0 -0
  163. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/apps.py +0 -0
  164. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/cacheops.py +0 -0
  165. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/caches.py +0 -0
  166. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/checks.py +0 -0
  167. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/constants.py +0 -0
  168. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/context.py +0 -0
  169. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/controllers.py +0 -0
  170. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/exceptions.py +0 -0
  171. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/locale/fr/LC_MESSAGES/django.mo +0 -0
  172. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/locale/fr/LC_MESSAGES/django.po +0 -0
  173. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/management/commands/init_oxiliere_system.py +0 -0
  174. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/permissions.py +0 -0
  175. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/schemas.py +0 -0
  176. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/settings.py +0 -0
  177. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/tests.py +0 -0
  178. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/utils.py +0 -0
  179. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pagination/cursor.py +0 -0
  180. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pdf/__init__.py +0 -0
  181. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pdf/printer.py +0 -0
  182. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pdf/utils.py +0 -0
  183. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pdf/views.py +0 -0
  184. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/actions.py +0 -0
  185. {oxutils-0.1.22/src/oxutils/users → oxutils-0.3.2/src/oxutils/permissions}/admin.py +0 -0
  186. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/apps.py +0 -0
  187. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/caches.py +0 -0
  188. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/checks.py +0 -0
  189. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/controllers.py +0 -0
  190. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/exceptions.py +0 -0
  191. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/locale/fr/LC_MESSAGES/django.mo +0 -0
  192. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/locale/fr/LC_MESSAGES/django.po +0 -0
  193. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/management/commands/load_permission_preset.py +0 -0
  194. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0001_initial.py +0 -0
  195. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0002_alter_grant_role.py +0 -0
  196. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +0 -0
  197. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0004_group_app_group_permissions_app_9a9924_idx.py +0 -0
  198. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0005_role_app.py +0 -0
  199. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0006_alter_rolegrant_options_and_more.py +0 -0
  200. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0007_grant_locked_alter_grant_role_and_more.py +0 -0
  201. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0008_remove_grant_unique_user_scope_role_and_more.py +0 -0
  202. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/perms.py +0 -0
  203. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/queryset.py +0 -0
  204. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/schemas.py +0 -0
  205. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/services.py +0 -0
  206. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/tests.py +0 -0
  207. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/settings.py +0 -0
  208. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/types.py +0 -0
  209. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/apps.py +0 -0
  210. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/locale/fr/LC_MESSAGES/django.mo +0 -0
  211. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/locale/fr/LC_MESSAGES/django.po +0 -0
  212. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/migrations/0001_initial.py +0 -0
  213. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +0 -0
  214. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/migrations/0003_user_photo.py +0 -0
  215. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/migrations/0004_alter_user_options.py +0 -0
  216. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/models.py +0 -0
  217. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/tests.py +0 -0
  218. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/utils.py +0 -0
  219. {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/utils.py +0 -0
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxutils
3
- Version: 0.1.22
3
+ Version: 0.3.2
4
4
  Summary: Production-ready utilities for Django applications in the Oxiliere ecosystem
5
5
  Keywords: django,utilities,jwt,audit,logging,celery,structlog
6
6
  Author: Edimedia Mutoke
7
7
  Author-email: Edimedia Mutoke <eddycondor07@gmail.com>
8
- License-Expression: Apache-2.0
8
+ License-Expression: LGPL-3.0-only
9
9
  Classifier: Development Status :: 4 - Beta
10
10
  Classifier: Framework :: Django
11
11
  Classifier: Framework :: Django :: 5.0
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3.12
@@ -20,14 +20,17 @@ Requires-Dist: bcc-rates==1.1.1
20
20
  Requires-Dist: boto3>=1.41.5
21
21
  Requires-Dist: celery>=5.5.3
22
22
  Requires-Dist: cryptography>=46.0.3
23
+ Requires-Dist: django-allauth[idp-oidc,mfa]>=65.18.0
23
24
  Requires-Dist: django-auditlog>=3.3.0
24
25
  Requires-Dist: django-celery-results>=2.6.0
25
26
  Requires-Dist: django-extensions>=4.1
26
27
  Requires-Dist: django-ninja>=1.5.0
27
28
  Requires-Dist: django-ninja-extra>=0.30.6
28
29
  Requires-Dist: django-structlog[celery]>=10.0.0
30
+ Requires-Dist: django-user-agents>=0.4.0
29
31
  Requires-Dist: jwcrypto>=1.5.6
30
32
  Requires-Dist: pydantic-settings>=2.12.0
33
+ Requires-Dist: pydantic[email]>=2.12.5
31
34
  Requires-Dist: pyjwt>=2.10.1
32
35
  Requires-Dist: requests>=2.32.5
33
36
  Requires-Dist: bcc-rates>=1.1.0 ; extra == 'all'
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "oxutils"
3
- version = "0.1.22"
3
+ version = "0.3.2"
4
4
  description = "Production-ready utilities for Django applications in the Oxiliere ecosystem"
5
5
  readme = "README.md"
6
- license = "Apache-2.0"
6
+ license = "LGPL-3.0-only"
7
7
  authors = [
8
8
  { name = "Edimedia Mutoke", email = "eddycondor07@gmail.com" }
9
9
  ]
@@ -14,7 +14,7 @@ classifiers = [
14
14
  "Framework :: Django",
15
15
  "Framework :: Django :: 5.0",
16
16
  "Intended Audience :: Developers",
17
- "License :: OSI Approved :: Apache Software License",
17
+ "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
18
18
  "Operating System :: OS Independent",
19
19
  "Programming Language :: Python :: 3",
20
20
  "Programming Language :: Python :: 3.12",
@@ -26,14 +26,17 @@ dependencies = [
26
26
  "boto3>=1.41.5",
27
27
  "celery>=5.5.3",
28
28
  "cryptography>=46.0.3",
29
+ "django-allauth[idp-oidc,mfa]>=65.18.0",
29
30
  "django-auditlog>=3.3.0",
30
31
  "django-celery-results>=2.6.0",
31
32
  "django-extensions>=4.1",
32
33
  "django-ninja>=1.5.0",
33
34
  "django-ninja-extra>=0.30.6",
34
35
  "django-structlog[celery]>=10.0.0",
36
+ "django-user-agents>=0.4.0",
35
37
  "jwcrypto>=1.5.6",
36
38
  "pydantic-settings>=2.12.0",
39
+ "pydantic[email]>=2.12.5",
37
40
  "pyjwt>=2.10.1",
38
41
  "requests>=2.32.5",
39
42
  ]
@@ -128,5 +131,12 @@ pythonpath = [".", "src"]
128
131
  python_files = ["test_*.py"]
129
132
  python_classes = ["Test*"]
130
133
  python_functions = ["test_*"]
131
- addopts = "-v --tb=short --ds=tests.settings"
134
+ addopts = "-v --tb=short --ds=tests.settings --strict-markers"
132
135
  testpaths = ["tests"]
136
+ filterwarnings = [
137
+ "ignore::DeprecationWarning:django.*",
138
+ "ignore::DeprecationWarning:pydantic.*",
139
+ "ignore::PendingDeprecationWarning:",
140
+ "ignore::pydantic.PydanticDeprecatedSince20:ninja.*",
141
+ "ignore::pydantic.PydanticDeprecatedSince20:ninja_extra.*",
142
+ ]
@@ -0,0 +1,28 @@
1
+ """OxUtils - Production-ready utilities for Django applications.
2
+
3
+ This package provides:
4
+ - JWT authentication with JWKS support
5
+ - Multi-tenant architecture (django-tenants integration, middleware, signals)
6
+ - Auth system: invitations, MFA, password reset, registration, sessions
7
+ - Structured logging with correlation IDs
8
+ - Audit system with S3 export
9
+ - Celery integration
10
+ - Django model mixins (ChangeTracker, CookieToken, SafeDelete)
11
+ - Permission management & authorization
12
+ - PDF generation
13
+ - Pagination utilities
14
+ - Currency utilities
15
+ - Custom exceptions, enums, and type definitions
16
+ """
17
+
18
+ __version__ = "0.3.1"
19
+
20
+ from oxutils.conf import AUDIT_MIDDLEWARE, UTILS_APPS
21
+ from oxutils.settings import oxi_settings
22
+
23
+ __all__ = [
24
+ "oxi_settings",
25
+ "UTILS_APPS",
26
+ "AUDIT_MIDDLEWARE",
27
+ "__version__",
28
+ ]
@@ -0,0 +1,212 @@
1
+ from allauth.account import app_settings as allauth_app_settings
2
+ from allauth.account.adapter import DefaultAccountAdapter
3
+ from allauth.account.models import EmailAddress
4
+ from allauth.core import context as allauth_ctx
5
+ from django.contrib.sites.shortcuts import get_current_site
6
+ from django.core.mail import EmailMessage, EmailMultiAlternatives
7
+ from django.template import TemplateDoesNotExist
8
+ from django.template.loader import render_to_string
9
+ from django.utils.translation import gettext_lazy as _
10
+
11
+ from oxutils.auth.utils import format_confirm_email_url, get_template_path
12
+
13
+
14
+ class JWTAllAuthAdapter(DefaultAccountAdapter):
15
+ """
16
+ Custom account adapter extending allauth's DefaultAccountAdapter with JWT-specific email handling.
17
+
18
+ Provides enhanced email confirmation functionality with template path customization
19
+ and JWT-related email content handling.
20
+
21
+ Key Features:
22
+
23
+ - Email normalization (trimming and lowercasing)
24
+ - Customizable template paths for verification emails
25
+ - Dual template support (HTML/text) with fallback handling
26
+ - Integration with JWT verification workflows
27
+ """
28
+
29
+ def clean_email(self, email):
30
+ """
31
+ Normalize email addresses by trimming whitespace and converting to lowercase.
32
+
33
+ Args:
34
+ email (str): Raw email input
35
+
36
+ Returns:
37
+ str: Normalized email address
38
+ """
39
+ email = super().clean_email(email)
40
+ email = email.strip().lower()
41
+
42
+ if allauth_app_settings.UNIQUE_EMAIL:
43
+ if EmailAddress.objects.filter(email=email, verified=True).exists():
44
+ raise ValueError(_("A user is already registered with this e-mail address."))
45
+ # delete previous non-verified registration attempts
46
+ EmailAddress.objects.filter(email=email, verified=False).delete()
47
+
48
+ return email
49
+
50
+ def populate_username(self, request, user):
51
+ """
52
+ Override username population since our User model doesn't use usernames.
53
+
54
+ Our User model has username = None and uses email as USERNAME_FIELD,
55
+ so we skip the username generation process entirely.
56
+
57
+ Args:
58
+ request (HttpRequest): Current request object
59
+ user (User): User instance to populate
60
+ """
61
+ # Do nothing - our User model doesn't use usernames
62
+ pass
63
+
64
+ def get_email_confirmation_url(self, request, emailconfirmation):
65
+ return format_confirm_email_url(emailconfirmation.key)
66
+
67
+ def send_confirmation_mail(self, request, emailconfirmation, signup):
68
+ """
69
+ Generate and send email confirmation message with context customization.
70
+
71
+ Context Includes:
72
+
73
+ - User object
74
+ - Verification code or URL (based on EMAIL_VERIFICATION_BY_CODE_ENABLED)
75
+ - Site-specific information
76
+
77
+ Args:
78
+ request (HttpRequest): Current request object
79
+ emailconfirmation (EmailConfirmation): Email confirmation instance
80
+ signup (bool): Flag indicating if this is a signup confirmation
81
+
82
+ Returns:
83
+ str: Confirmation key used in the email
84
+ """
85
+ ctx = {
86
+ "user": emailconfirmation.email_address.user,
87
+ }
88
+ if allauth_app_settings.EMAIL_VERIFICATION_BY_CODE_ENABLED:
89
+ ctx.update({"code": emailconfirmation.key})
90
+ else:
91
+ ctx.update(
92
+ {
93
+ "key": emailconfirmation.key,
94
+ "activate_url": self.get_email_confirmation_url(request, emailconfirmation),
95
+ }
96
+ )
97
+ if signup:
98
+ email_template = "email/signup/email_signup"
99
+ template_path = get_template_path(
100
+ "EMAIL_VERIFICATION", "email/signup/email_message.html"
101
+ )
102
+ subject_path = get_template_path(
103
+ "EMAIL_VERIFICATION_SUBJECT", "email/signup/email_subject.txt"
104
+ )
105
+ else:
106
+ email_template = "account/email/email_confirmation"
107
+ template_path = None
108
+ subject_path = None
109
+ self.send_mail(
110
+ email_template,
111
+ emailconfirmation.email_address.email,
112
+ ctx,
113
+ subject_path=subject_path,
114
+ template_path=template_path,
115
+ )
116
+ return ctx["key"]
117
+
118
+ def send_mail(self, template_prefix, email, context, subject_path=None, template_path=None):
119
+ """
120
+ Construct and send email using template configuration.
121
+
122
+ Enhances Context With:
123
+
124
+ - Current site information
125
+ - Recipient email address
126
+
127
+ Args:
128
+ template_prefix (str): Base path for template lookup
129
+ email (str|list): Recipient email address(es)
130
+ context (dict): Template context variables
131
+ subject_path (str, optional): Custom path for subject template
132
+ template_path (str, optional): Custom path for body template
133
+ """
134
+ ctx = {
135
+ "email": email,
136
+ "current_site": get_current_site(allauth_ctx.request),
137
+ }
138
+ ctx.update(context)
139
+ msg = self.render_mail(
140
+ template_prefix, email, ctx, subject_path=subject_path, template_path=template_path
141
+ )
142
+ msg.send()
143
+
144
+ def render_mail(
145
+ self, template_prefix, email, context, headers=None, subject_path=None, template_path=None
146
+ ):
147
+ """
148
+ Render email message with support for multiple template formats and custom paths.
149
+
150
+ Behavior:
151
+
152
+ - Generates multipart emails when both HTML and text templates exist
153
+ - Uses custom template paths when provided
154
+ - Automatically formats email subject
155
+ - Supports HTML email content as primary when specified
156
+
157
+ Args:
158
+ template_prefix (str): Base template path prefix
159
+ email (str|list): Recipient email address(es)
160
+ context (dict): Template context variables
161
+ headers (dict, optional): Custom email headers
162
+ subject_path (str, optional): Override path for subject template
163
+ template_path (str, optional): Override path for body template
164
+
165
+ Returns:
166
+ EmailMessage: Configured email message object
167
+
168
+ Raises:
169
+ TemplateDoesNotExist: If no valid template can be found
170
+ """
171
+ to = [email] if isinstance(email, str) else email
172
+ if subject_path is None:
173
+ subject_path = "{0}_subject.txt".format(template_prefix)
174
+ subject = render_to_string(subject_path, context)
175
+ # remove superfluous line breaks
176
+ subject = " ".join(subject.splitlines()).strip()
177
+ subject = self.format_email_subject(subject)
178
+
179
+ from_email = self.get_from_email()
180
+
181
+ if template_path is None:
182
+ bodies = {}
183
+ html_ext = allauth_app_settings.TEMPLATE_EXTENSION
184
+ for ext in [html_ext, "txt"]:
185
+ try:
186
+ template_name = "{0}_message.{1}".format(template_prefix, ext)
187
+ bodies[ext] = render_to_string(
188
+ template_name,
189
+ context,
190
+ allauth_ctx.request,
191
+ ).strip()
192
+ except TemplateDoesNotExist:
193
+ if ext == "txt" and not bodies:
194
+ # We need at least one body
195
+ raise
196
+ else:
197
+ html_ext = "html"
198
+ bodies = {
199
+ html_ext: render_to_string(
200
+ template_path,
201
+ context,
202
+ allauth_ctx.request,
203
+ ).strip()
204
+ }
205
+ if "txt" in bodies:
206
+ msg = EmailMultiAlternatives(subject, bodies["txt"], from_email, to, headers=headers)
207
+ if html_ext in bodies:
208
+ msg.attach_alternative(bodies[html_ext], "text/html")
209
+ else:
210
+ msg = EmailMessage(subject, bodies[html_ext], from_email, to, headers=headers)
211
+ msg.content_subtype = "html" # Main content is now text/html
212
+ return msg
@@ -0,0 +1,25 @@
1
+ from django.conf import settings
2
+
3
+ from oxutils.auth.password_reset.schemas import PasswordResetSchema as DefaultPasswordResetSchema
4
+ from oxutils.auth.registration.schemas import RegisterSchema as DefaultRegisterSchema
5
+ from oxutils.auth.schemas import (
6
+ LoginSchema as DefaultLoginSchema,
7
+ )
8
+ from oxutils.auth.schemas import (
9
+ PasswordChangeSchema as DefaultPasswordChangeSchema,
10
+ )
11
+ from oxutils.auth.utils import import_callable
12
+
13
+ schemas = getattr(settings, "JWT_ALLAUTH_SCHEMAS", {})
14
+
15
+ LoginSerializer = import_callable(schemas.get("LOGIN_SCHEMA", DefaultLoginSchema))
16
+
17
+ PasswordResetSerializer = import_callable(
18
+ schemas.get("PASSWORD_RESET_SCHEMA", DefaultPasswordResetSchema)
19
+ )
20
+
21
+ PasswordChangeSerializer = import_callable(
22
+ schemas.get("PASSWORD_CHANGE_SCHEMA", DefaultPasswordChangeSchema)
23
+ )
24
+
25
+ RegisterSerializer = import_callable(schemas.get("REGISTER_SCHEMA", DefaultRegisterSchema))
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class OxiAuthConfig(AppConfig):
5
+ name = 'oxutils.auth'
6
+ label = 'oxi_auth'
@@ -0,0 +1,14 @@
1
+ PASS_RESET = 'PASS_RESET'
2
+ PASS_RESET_ACCESS = 'PASS_RESET_ACCESS'
3
+ TEMPLATE_PATHS = 'JWT_ALLAUTH_TEMPLATES'
4
+
5
+ EMAIL_VERIFIED_REDIRECT = 'EMAIL_VERIFIED_REDIRECT'
6
+ PASSWORD_RESET_REDIRECT = 'PASSWORD_RESET_REDIRECT'
7
+
8
+ PASS_RESET_COOKIE = 'password_reset_access_token'
9
+
10
+ FOR_USER = 'for_user'
11
+ ONE_TIME_PERMISSION = 'one_time_permission'
12
+
13
+ REFRESH_TOKEN_COOKIE = 'refresh_token'
14
+ ACCESS_TOKEN_COOKIE = 'access_token'
@@ -0,0 +1,203 @@
1
+ from typing import Optional
2
+
3
+ from django.http import HttpRequest
4
+ from django.utils.translation import gettext_lazy as _
5
+ from ninja_extra import ControllerBase, api_controller, http_post
6
+ from ninja_extra.permissions import IsAuthenticated
7
+ from ninja_extra.throttling import AnonRateThrottle, UserRateThrottle
8
+ from ninja_jwt.exceptions import InvalidToken, TokenError
9
+ from ninja_jwt.schema_control import SchemaControl
10
+ from ninja_jwt.settings import api_settings
11
+
12
+ from oxutils.auth.invitations.controllers import InvitationController
13
+ from oxutils.auth.mixins import CookieTokenMixin
14
+ from oxutils.auth.signals import user_logged_in, user_logged_out
15
+ from oxutils.auth.password_reset.controllers import (
16
+ PasswordResetConfirmController,
17
+ PasswordResetController,
18
+ ResetNewPasswordController,
19
+ )
20
+ from oxutils.auth.registration.controllers import (
21
+ EmailVerificationController,
22
+ RegisterController,
23
+ )
24
+ from oxutils.auth.schemas import (
25
+ LoginSchema,
26
+ PasswordChangeResponseSchema,
27
+ PasswordChangeSchema,
28
+ ReauthenticatePasswordSchema,
29
+ RemoveRefreshTokenSchema,
30
+ SendVerificationEmailSchema,
31
+ TokenObtainMFARequiredSchema,
32
+ TokenObtainPairOutputSchema,
33
+ TokenRefreshInputSchema,
34
+ TokenRefreshOutputSchema,
35
+ TokenRefreshSchema,
36
+ )
37
+ from oxutils.auth.utils import (
38
+ get_refresh_token,
39
+ get_user_agent,
40
+ load_user,
41
+ sensitive_post_parameters_m,
42
+ user_agent_dict,
43
+ )
44
+ from oxutils.exceptions import ExceptionCode
45
+ from oxutils.mixins.schemas import ResponseSchema
46
+
47
+ schema = SchemaControl(api_settings)
48
+
49
+
50
+ class LoginController(ControllerBase, CookieTokenMixin):
51
+ auto_import = False
52
+
53
+ @http_post(
54
+ "/login",
55
+ response={200: TokenObtainPairOutputSchema, 202: TokenObtainMFARequiredSchema},
56
+ url_name="token_obtain_pair",
57
+ operation_id="token_obtain_pair",
58
+ throttle=[AnonRateThrottle()],
59
+ auth=None,
60
+ )
61
+ def obtain_token(self, request: HttpRequest, user_token: LoginSchema):
62
+ user_token.check_user_authentication_rule()
63
+ schema = user_token.to_response_schema()
64
+
65
+ if schema.is_mfa_required():
66
+ return 202, schema
67
+
68
+ self.set_token_cookie(access_token=schema.access, refresh_token=schema.refresh)
69
+
70
+ user_logged_in.send_robust(sender=self.__class__, request=request, user=user_token._user)
71
+
72
+ return schema
73
+
74
+ @http_post(
75
+ "/verify-email",
76
+ response={
77
+ 200: ResponseSchema,
78
+ },
79
+ throttle=[AnonRateThrottle()],
80
+ auth=None,
81
+ )
82
+ def send_verification_email(self, request: HttpRequest, payload: SendVerificationEmailSchema):
83
+ payload.send_verification_email(request)
84
+
85
+ return ResponseSchema(
86
+ code=ExceptionCode.SUCCESS, detail=str(_("Verification email sent successfully."))
87
+ )
88
+
89
+
90
+ class ReauthenticateController(ControllerBase, CookieTokenMixin):
91
+ auto_import = False
92
+
93
+ @http_post(
94
+ "/reauthenticate",
95
+ response={
96
+ 200: TokenObtainPairOutputSchema,
97
+ },
98
+ url_name="reauthenticate_password",
99
+ operation_id="reauthenticate_password",
100
+ throttle=[UserRateThrottle()],
101
+ permissions=[IsAuthenticated],
102
+ )
103
+ @load_user
104
+ def reauthenticate(self, request: HttpRequest, payload: ReauthenticatePasswordSchema):
105
+ schema = payload.authenticate(request)
106
+
107
+ self.set_token_cookie(access_token=schema.access, refresh_token=schema.refresh)
108
+
109
+ user_logged_in.send_robust(sender=self.__class__, request=request, user=request.user)
110
+
111
+ return schema
112
+
113
+
114
+ class LogoutController(ControllerBase, CookieTokenMixin):
115
+ """
116
+ Calls Django logout method and delete the Token object
117
+ assigned to the current User object.
118
+
119
+ Accepts/Returns nothing.
120
+ """
121
+
122
+ @http_post("/logout", permissions=[IsAuthenticated], response={200: dict, 401: dict})
123
+ def logout(self, request: HttpRequest):
124
+ input_data = {"user": request.user.id}
125
+
126
+ refresh_token = get_refresh_token(request)
127
+
128
+ if not refresh_token:
129
+ raise InvalidToken
130
+
131
+ input_data["refresh"] = refresh_token
132
+
133
+ try:
134
+ RemoveRefreshTokenSchema.model_validate(input_data, context={"user": request.user.id})
135
+ self.remove_token_cookie()
136
+ user_logged_out.send_robust(sender=self.__class__, request=request, user=request.user)
137
+ return 200, {"detail": _("Successfully logged out.")}
138
+ except (TokenError, InvalidToken) as exc:
139
+ return 401, {"detail": _("Invalid token.")}
140
+
141
+
142
+ class PasswordChangeController(ControllerBase):
143
+ @http_post(
144
+ "/change_password",
145
+ permissions=[IsAuthenticated],
146
+ throttle=[UserRateThrottle()],
147
+ response=PasswordChangeResponseSchema,
148
+ )
149
+ @sensitive_post_parameters_m
150
+ @load_user
151
+ def change_user_password(self, request: HttpRequest, payload: PasswordChangeSchema):
152
+ return payload.get_response(request)
153
+
154
+
155
+ class TokenRefreshController(ControllerBase, CookieTokenMixin):
156
+ @http_post(
157
+ "/refresh",
158
+ auth=None,
159
+ response={200: TokenRefreshOutputSchema, 401: dict},
160
+ url_name="token_refresh",
161
+ operation_id="token_refresh",
162
+ throttle=[AnonRateThrottle()],
163
+ )
164
+ @get_user_agent
165
+ def refresh_token(self, request: HttpRequest, payload: Optional[TokenRefreshInputSchema]):
166
+ try:
167
+ input_data = {}
168
+ refresh_token = get_refresh_token(request)
169
+
170
+ if not refresh_token:
171
+ if payload.refresh is None:
172
+ raise InvalidToken
173
+
174
+ refresh_token = payload.refresh
175
+
176
+ input_data["refresh"] = refresh_token
177
+ ctx = user_agent_dict(request)
178
+ tokens = TokenRefreshSchema.model_validate(input_data, context=ctx)
179
+
180
+ self.set_token_cookie(access_token=tokens["access"], refresh_token=tokens["refresh"])
181
+
182
+ return tokens
183
+ except (TokenError, InvalidToken) as exc:
184
+ return 401, {"detail": _("Invalid token.")}
185
+
186
+
187
+ @api_controller(
188
+ "/auth",
189
+ )
190
+ class AuthController(
191
+ LoginController,
192
+ ReauthenticateController,
193
+ LogoutController,
194
+ PasswordChangeController,
195
+ TokenRefreshController,
196
+ EmailVerificationController,
197
+ RegisterController,
198
+ InvitationController,
199
+ PasswordResetConfirmController,
200
+ PasswordResetController,
201
+ ResetNewPasswordController,
202
+ ):
203
+ pass