django-microsys 2.2.2__tar.gz → 2.2.3__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 (263) hide show
  1. {django_microsys-2.2.2 → django_microsys-2.2.3}/PKG-INFO +1 -1
  2. {django_microsys-2.2.2 → django_microsys-2.2.3}/django_microsys.egg-info/PKG-INFO +1 -1
  3. django_microsys-2.2.3/microsys/VERSION +1 -0
  4. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/utils.py +66 -63
  5. django_microsys-2.2.2/microsys/VERSION +0 -1
  6. {django_microsys-2.2.2 → django_microsys-2.2.3}/LICENSE +0 -0
  7. {django_microsys-2.2.2 → django_microsys-2.2.3}/MANIFEST.in +0 -0
  8. {django_microsys-2.2.2 → django_microsys-2.2.3}/README.md +0 -0
  9. {django_microsys-2.2.2 → django_microsys-2.2.3}/django_microsys.egg-info/SOURCES.txt +0 -0
  10. {django_microsys-2.2.2 → django_microsys-2.2.3}/django_microsys.egg-info/dependency_links.txt +0 -0
  11. {django_microsys-2.2.2 → django_microsys-2.2.3}/django_microsys.egg-info/entry_points.txt +0 -0
  12. {django_microsys-2.2.2 → django_microsys-2.2.3}/django_microsys.egg-info/requires.txt +0 -0
  13. {django_microsys-2.2.2 → django_microsys-2.2.3}/django_microsys.egg-info/top_level.txt +0 -0
  14. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/__init__.py +0 -0
  15. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/__main__.py +0 -0
  16. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/admin.py +0 -0
  17. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/api.py +0 -0
  18. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/apps.py +0 -0
  19. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/cli.py +0 -0
  20. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/constants.py +0 -0
  21. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/context_processors.py +0 -0
  22. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/discovery.py +0 -0
  23. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/fetcher.py +0 -0
  24. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/filters.py +0 -0
  25. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/fonts.py +0 -0
  26. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/formats/__init__.py +0 -0
  27. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/formats/ar/__init__.py +0 -0
  28. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/formats/ar/formats.py +0 -0
  29. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/formats/en/__init__.py +0 -0
  30. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/formats/en/formats.py +0 -0
  31. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/forms.py +0 -0
  32. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/guards.py +0 -0
  33. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/management/commands/__init__.py +0 -0
  34. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/management/commands/microsys_check.py +0 -0
  35. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/management/commands/microsys_setup.py +0 -0
  36. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/management/commands/migrator.py +0 -0
  37. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/management/commands/seed_activity_log.py +0 -0
  38. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/managers.py +0 -0
  39. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/middleware.py +0 -0
  40. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/migrations/0001_initial.py +0 -0
  41. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/migrations/0002_public_registration.py +0 -0
  42. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/migrations/0003_public_root_split.py +0 -0
  43. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/migrations/0004_client_ip_and_trusted_devices.py +0 -0
  44. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/migrations/0005_systemsettings_allow_user_font_override_and_more.py +0 -0
  45. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/migrations/__init__.py +0 -0
  46. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/models.py +0 -0
  47. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/patches.py +0 -0
  48. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/registration.py +0 -0
  49. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold.py +0 -0
  50. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/README.md.tmpl +0 -0
  51. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/__init__.py.tmpl +0 -0
  52. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/apps.py.tmpl +0 -0
  53. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/filters.py.tmpl +0 -0
  54. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/forms.py.tmpl +0 -0
  55. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/migrations/__init__.py.tmpl +0 -0
  56. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/models.py.tmpl +0 -0
  57. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/tables.py.tmpl +0 -0
  58. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/templates/example_record_confirm_delete.html.tmpl +0 -0
  59. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/templates/example_record_detail.html.tmpl +0 -0
  60. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/templates/example_record_form.html.tmpl +0 -0
  61. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/templates/example_record_list.html.tmpl +0 -0
  62. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/tests/__init__.py.tmpl +0 -0
  63. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/tests/test_app.py.tmpl +0 -0
  64. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/translations.py.tmpl +0 -0
  65. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/urls.py.tmpl +0 -0
  66. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/app/views.py.tmpl +0 -0
  67. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/.nginx/nginx.conf.tmpl +0 -0
  68. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/.secrets/.env.tmpl +0 -0
  69. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/Dockerfile.tmpl +0 -0
  70. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/README.md.tmpl +0 -0
  71. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/compose.dev.yml.tmpl +0 -0
  72. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/compose.yml.tmpl +0 -0
  73. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/dockerignore.tmpl +0 -0
  74. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/docs/README.md.tmpl +0 -0
  75. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/entrypoint.sh.tmpl +0 -0
  76. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/gitattributes.tmpl +0 -0
  77. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/gitignore.tmpl +0 -0
  78. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/gunicorn.py.tmpl +0 -0
  79. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/manage.py.tmpl +0 -0
  80. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/package/__init__.py.tmpl +0 -0
  81. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/package/asgi.py.tmpl +0 -0
  82. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/package/celery.py.tmpl +0 -0
  83. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/package/settings.py.tmpl +0 -0
  84. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/package/urls.py.tmpl +0 -0
  85. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/package/wsgi.py.tmpl +0 -0
  86. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/req.txt.tmpl +0 -0
  87. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/start.ps1.tmpl +0 -0
  88. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/start.sh.tmpl +0 -0
  89. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/tests/__init__.py.tmpl +0 -0
  90. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/tests/test_scaffold.py.tmpl +0 -0
  91. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/scaffold_templates/project/tools/smtp_relay.py.tmpl +0 -0
  92. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/signals.py +0 -0
  93. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap-icons.css +0 -0
  94. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap-icons.woff +0 -0
  95. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap-icons.woff2 +0 -0
  96. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap.bundle.min.js +0 -0
  97. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap.bundle.min.js.map +0 -0
  98. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap.min.css +0 -0
  99. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap.min.css.map +0 -0
  100. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap.rtl.min.css +0 -0
  101. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/bootstrap/bootstrap.rtl.min.css.map +0 -0
  102. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/favicon.ico +0 -0
  103. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/img/base_logo.webp +0 -0
  104. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/img/default_profile.webp +0 -0
  105. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/img/login_logo.webp +0 -0
  106. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/accessibility/css/main.css +0 -0
  107. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/accessibility/js/main.js +0 -0
  108. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/activitylog/js/main.js +0 -0
  109. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/fonts/TwemojiCountryFlags.woff2 +0 -0
  110. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/fonts/cairo-bold.woff2 +0 -0
  111. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/fonts/cairo-medium.woff2 +0 -0
  112. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/fonts/cairo-regular.woff2 +0 -0
  113. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/fonts/shabwa-bold.woff2 +0 -0
  114. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/fonts/shabwa-medium.woff2 +0 -0
  115. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/fonts/shabwa-regular.woff2 +0 -0
  116. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/forms/css/file_field.css +0 -0
  117. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/forms/css/form_actions.css +0 -0
  118. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/forms/css/form_fields.css +0 -0
  119. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/forms/js/file_field.js +0 -0
  120. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/forms/js/filter_form.js +0 -0
  121. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/autofill/js/main.js +0 -0
  122. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/context_menu/css/main.css +0 -0
  123. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/context_menu/js/main.js +0 -0
  124. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/context_menu/js/section_manager.js +0 -0
  125. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/dynamic_modal/js/main.js +0 -0
  126. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/prevent_double_submit.js +0 -0
  127. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/scan_link/js/main.js +0 -0
  128. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/scan_link/js/scan_button.js +0 -0
  129. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/helpers/wizard/js/main.js +0 -0
  130. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/language/css/main.css +0 -0
  131. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/language/js/main.js +0 -0
  132. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/language/js/translations.js +0 -0
  133. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/buttons.css +0 -0
  134. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/dropdowns.css +0 -0
  135. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/index_cards.css +0 -0
  136. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/main.css +0 -0
  137. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/options.css +0 -0
  138. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/pagination.css +0 -0
  139. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/selectors.css +0 -0
  140. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/system_setup.css +0 -0
  141. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/tables.css +0 -0
  142. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/template_cleanup.css +0 -0
  143. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/css/titlebar.css +0 -0
  144. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/js/base_head.js +0 -0
  145. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/js/base_runtime.js +0 -0
  146. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/js/options.js +0 -0
  147. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/js/selectors.js +0 -0
  148. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/js/system_setup.js +0 -0
  149. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/main/js/tables.js +0 -0
  150. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/sections/js/manage_sections.js +0 -0
  151. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/sidebar/css/main.css +0 -0
  152. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/sidebar/css/reorder.css +0 -0
  153. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/sidebar/css/theme_picker.css +0 -0
  154. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/sidebar/js/main.js +0 -0
  155. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/sidebar/js/preload.js +0 -0
  156. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/sidebar/js/reorder.js +0 -0
  157. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/sidebar/js/theme_picker.js +0 -0
  158. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/blue.css +0 -0
  159. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/dark.css +0 -0
  160. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/gold.css +0 -0
  161. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/gothic.css +0 -0
  162. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/green.css +0 -0
  163. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/light.css +0 -0
  164. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/mono.css +0 -0
  165. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/neon.css +0 -0
  166. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/red.css +0 -0
  167. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/css/retro.css +0 -0
  168. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/themes/js/main.js +0 -0
  169. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/tutorial/css/main.css +0 -0
  170. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/tutorial/js/driver.js +0 -0
  171. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/tutorial/js/main.js +0 -0
  172. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/css/login.css +0 -0
  173. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/css/permissions.css +0 -0
  174. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/css/profile.css +0 -0
  175. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/css/user_hub.css +0 -0
  176. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/js/login.js +0 -0
  177. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/js/manage_users.js +0 -0
  178. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/js/permissions.js +0 -0
  179. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/js/profile_2fa.js +0 -0
  180. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/js/profile_image_widget.js +0 -0
  181. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/js/twofa_verify.js +0 -0
  182. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/microsys/users/js/user_hub.js +0 -0
  183. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/vanillajs-datepicker/datepicker-bs5.min.css +0 -0
  184. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/static/vanillajs-datepicker/datepicker.min.js +0 -0
  185. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/tables.py +0 -0
  186. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/bootstrap5/layout/field_file.html +0 -0
  187. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/2fa/verify.html +0 -0
  188. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/activitylog/activity_log.html +0 -0
  189. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/activitylog/activity_log_detail_modal.html +0 -0
  190. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/base.html +0 -0
  191. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/form_base.html +0 -0
  192. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/forms/assets_head.html +0 -0
  193. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/forms/assets_scripts.html +0 -0
  194. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/forms/crispy_file_field.html +0 -0
  195. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/forms/file_input.html +0 -0
  196. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/forms/filter_assets_head.html +0 -0
  197. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/forms/filter_assets_scripts.html +0 -0
  198. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/helpers/dynamic_modal.html +0 -0
  199. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/helpers/dynamic_modal_combined.html +0 -0
  200. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/helpers/dynamic_modal_detail.html +0 -0
  201. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/helpers/dynamic_modal_form.html +0 -0
  202. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/helpers/dynamic_modal_list.html +0 -0
  203. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/helpers/micro_context_menu.html +0 -0
  204. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/font_previews.html +0 -0
  205. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/font_settings_matrix.html +0 -0
  206. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/language_catalog_editor.html +0 -0
  207. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/language_fonts_editor.html +0 -0
  208. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/language_previews.html +0 -0
  209. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/options.html +0 -0
  210. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/sidebar_builder.html +0 -0
  211. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/sidebar_density_previews.html +0 -0
  212. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/sidebar_items.html +0 -0
  213. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/system_names_editor.html +0 -0
  214. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/system_setup.html +0 -0
  215. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/table_density_previews.html +0 -0
  216. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/theme_previews.html +0 -0
  217. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/theme_settings_matrix.html +0 -0
  218. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/titlebar.html +0 -0
  219. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/translation_matrix_editor.html +0 -0
  220. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/includes/tutorial.html +0 -0
  221. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/list_base.html +0 -0
  222. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/registration/pending.html +0 -0
  223. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/scopes/scope_actions.html +0 -0
  224. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/scopes/scope_form.html +0 -0
  225. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/scopes/scope_manager.html +0 -0
  226. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/sections/manage_sections.html +0 -0
  227. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/sections/subsection_select.html +0 -0
  228. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/sidebar/auto.html +0 -0
  229. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/sidebar/extra_groups.html +0 -0
  230. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/sidebar/main.html +0 -0
  231. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/sidebar/tree.html +0 -0
  232. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/tables/table.html +0 -0
  233. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/users/grouped_permissions.html +0 -0
  234. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/users/manage_users.html +0 -0
  235. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/users/profile.html +0 -0
  236. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/users/profile_image_widget.html +0 -0
  237. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/users/user_detail_modal.html +0 -0
  238. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/microsys/users/user_hub.html +0 -0
  239. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/registration/email/verify_registration.txt +0 -0
  240. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/registration/login.html +0 -0
  241. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/registration/register.html +0 -0
  242. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/registration/register_sent.html +0 -0
  243. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templates/registration/register_verify.html +0 -0
  244. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templatetags/__init__.py +0 -0
  245. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templatetags/microsys_tags.py +0 -0
  246. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templatetags/microsys_translation.py +0 -0
  247. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/templatetags/sidebar_tags.py +0 -0
  248. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/themes.py +0 -0
  249. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/translations.py +0 -0
  250. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/urls.py +0 -0
  251. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/__init__.py +0 -0
  252. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/activitylog.py +0 -0
  253. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/general.py +0 -0
  254. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/profile.py +0 -0
  255. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/registration.py +0 -0
  256. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/scopes.py +0 -0
  257. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/sections.py +0 -0
  258. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/sidebar.py +0 -0
  259. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/twofa.py +0 -0
  260. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/views/users.py +0 -0
  261. {django_microsys-2.2.2 → django_microsys-2.2.3}/microsys/widgets.py +0 -0
  262. {django_microsys-2.2.2 → django_microsys-2.2.3}/pyproject.toml +0 -0
  263. {django_microsys-2.2.2 → django_microsys-2.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django_microsys
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: Django microSYS (System Integration Service) - Multilingual Django Starter Pack, Packed with Features.
5
5
  Author-email: DeBeski <debeski1@gmail.com>
6
6
  License: NON-COMMERCIAL
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-microsys
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: Django microSYS (System Integration Service) - Multilingual Django Starter Pack, Packed with Features.
5
5
  Author-email: DeBeski <debeski1@gmail.com>
6
6
  License: NON-COMMERCIAL
@@ -0,0 +1 @@
1
+ 2.2.3
@@ -8,7 +8,6 @@ from decimal import Decimal, InvalidOperation
8
8
  from functools import lru_cache
9
9
  import inspect
10
10
  import unicodedata
11
-
12
11
  from django import forms
13
12
  from django.apps import apps
14
13
  from django.conf import settings
@@ -22,7 +21,6 @@ from django.http import JsonResponse
22
21
  from django.core.mail import EmailMessage, get_connection, send_mail
23
22
  from django.core.exceptions import FieldDoesNotExist
24
23
  from django.utils.module_loading import import_string
25
-
26
24
  from .constants import (
27
25
  DEFAULT_HOME_URL,
28
26
  DEFAULT_SIDEBAR_COLLAPSE_MODE,
@@ -49,7 +47,6 @@ try:
49
47
  except ImportError:
50
48
  django_filters = None
51
49
 
52
-
53
50
  SENSITIVE_ACTIVITY_MASK = "********"
54
51
  _SENSITIVE_ACTIVITY_FIELD_NAMES = {
55
52
  "password",
@@ -67,7 +64,7 @@ _SENSITIVE_ACTIVITY_FIELD_NAMES = {
67
64
  "apitoken",
68
65
  }
69
66
 
70
-
67
+ # Normalize activity log model names for translation lookup
71
68
  def normalize_activity_log_model_key(value):
72
69
  raw = str(value or '').strip().lower()
73
70
  if not raw:
@@ -76,11 +73,11 @@ def normalize_activity_log_model_key(value):
76
73
  normalized = re.sub(r'_+', '_', normalized).strip('_')
77
74
  return normalized
78
75
 
79
-
76
+ # Normalize string for fuzzy model matching
80
77
  def _normalize_fuzzy_string(s):
81
78
  return unicodedata.normalize('NFKD', str(s)).casefold() if s else ""
82
79
 
83
-
80
+ # Fuzzy Model Lookup — Builds a cached mapping of normalized model names to model classes
84
81
  @lru_cache(maxsize=1)
85
82
  def _get_fuzzy_model_mapping():
86
83
  """Builds a cached mapping of normalized model names to model classes."""
@@ -102,7 +99,7 @@ def _get_fuzzy_model_mapping():
102
99
 
103
100
  return mapping
104
101
 
105
-
102
+ # Translate activity log model names using MS_TRANS
106
103
  def translate_activity_log_model_name(value, strings=None):
107
104
  if not value:
108
105
  return ""
@@ -122,7 +119,7 @@ def translate_activity_log_model_name(value, strings=None):
122
119
  return s[key]
123
120
  return value
124
121
 
125
-
122
+ # Coerce a setting value to a list
126
123
  def _coerce_list_setting(scope, key):
127
124
  value = scope.get(key)
128
125
  if value is None:
@@ -132,7 +129,7 @@ def _coerce_list_setting(scope, key):
132
129
  scope[key] = value
133
130
  return value
134
131
 
135
-
132
+ # Insert middleware at specified position, removing duplicates
136
133
  def _insert_middleware_once(middleware, middleware_path, *, after=None, before=None):
137
134
  if middleware_path in middleware:
138
135
  middleware.remove(middleware_path)
@@ -145,7 +142,7 @@ def _insert_middleware_once(middleware, middleware_path, *, after=None, before=N
145
142
 
146
143
  middleware.insert(insert_at, middleware_path)
147
144
 
148
-
145
+ # Secret Management — Reads a Docker secret first, then falls back to an environment variable
149
146
  def get_secret(secret_name, env_var):
150
147
  """Read a Docker secret first, then fall back to an environment variable."""
151
148
  secret_path = os.path.join("/run/secrets", secret_name)
@@ -155,7 +152,6 @@ def get_secret(secret_name, env_var):
155
152
  except OSError:
156
153
  return os.getenv(env_var)
157
154
 
158
-
159
155
  EMAIL_CONFIG_TRANSPORTS = {'direct', 'relay'}
160
156
  EMAIL_CONFIG_SECRET_STORAGES = {'env', 'encrypted_db'}
161
157
  MICROSYS_INTERNAL_SMTP_RELAY_HOST = 'smtp-relay'
@@ -173,7 +169,7 @@ CLIENT_IP_MODE_VALUES = {
173
169
  CLIENT_IP_MODE_CUSTOM,
174
170
  }
175
171
 
176
-
172
+ # Return default email configuration structure
177
173
  def default_email_config():
178
174
  return {
179
175
  'transport': 'direct',
@@ -188,7 +184,7 @@ def default_email_config():
188
184
  'password_configured': False,
189
185
  }
190
186
 
191
-
187
+ # Return default client IP resolution configuration
192
188
  def default_client_ip_config():
193
189
  return {
194
190
  'mode': CLIENT_IP_MODE_X_FORWARDED_FOR,
@@ -196,7 +192,7 @@ def default_client_ip_config():
196
192
  'custom_header': '',
197
193
  }
198
194
 
199
-
195
+ # Normalize custom header name for client IP resolution
200
196
  def _normalize_client_ip_header_name(value):
201
197
  raw_value = str(value or '').strip()
202
198
  if not raw_value:
@@ -208,7 +204,7 @@ def _normalize_client_ip_header_name(value):
208
204
  normalized = f'HTTP_{normalized}'
209
205
  return normalized
210
206
 
211
-
207
+ # Normalize and validate client IP configuration
212
208
  def normalize_client_ip_config(value):
213
209
  normalized = default_client_ip_config()
214
210
  if not isinstance(value, dict):
@@ -230,7 +226,7 @@ def normalize_client_ip_config(value):
230
226
 
231
227
  return normalized
232
228
 
233
-
229
+ # Normalize and validate email configuration
234
230
  def normalize_email_config(value, *, redact_secret=False):
235
231
  config = value if isinstance(value, dict) else {}
236
232
  normalized = default_email_config()
@@ -258,7 +254,7 @@ def normalize_email_config(value, *, redact_secret=False):
258
254
  normalized.pop('encrypted_password', None)
259
255
  return normalized
260
256
 
261
-
257
+ # Normalize allowed fonts list against available fonts
262
258
  def normalize_allowed_fonts(allowed_fonts=None):
263
259
  from .fonts import get_builtin_fonts
264
260
  available = {f['slug'] for f in get_builtin_fonts()}
@@ -273,7 +269,7 @@ def normalize_allowed_fonts(allowed_fonts=None):
273
269
 
274
270
  return normalized or list(available)
275
271
 
276
-
272
+ # Get seed key for email encryption
277
273
  def _email_secret_seed():
278
274
  configured_key = (
279
275
  os.getenv('MICROSYS_EMAIL_SECRET_KEY')
@@ -283,21 +279,21 @@ def _email_secret_seed():
283
279
  )
284
280
  return str(configured_key or 'microsys-email-secret-dev-key')
285
281
 
286
-
282
+ # Get Fernet instance for email encryption
287
283
  def _email_fernet():
288
284
  from cryptography.fernet import Fernet
289
285
 
290
286
  digest = hashlib.sha256(_email_secret_seed().encode('utf-8')).digest()
291
287
  return Fernet(base64.urlsafe_b64encode(digest))
292
288
 
293
-
289
+ # Encrypt email secret using Fernet
294
290
  def encrypt_email_secret(raw_secret):
295
291
  raw_secret = str(raw_secret or '')
296
292
  if not raw_secret:
297
293
  return ''
298
294
  return _email_fernet().encrypt(raw_secret.encode('utf-8')).decode('utf-8')
299
295
 
300
-
296
+ # Decrypt email secret using Fernet
301
297
  def decrypt_email_secret(encrypted_secret):
302
298
  encrypted_secret = str(encrypted_secret or '').strip()
303
299
  if not encrypted_secret:
@@ -307,11 +303,10 @@ def decrypt_email_secret(encrypted_secret):
307
303
  except Exception:
308
304
  return ''
309
305
 
310
-
311
306
  TOTP_SECRET_PREFIX = 'fernet$'
312
307
  _UNSET = object()
313
308
 
314
-
309
+ # Get seed key for TOTP encryption
315
310
  def _totp_secret_seed():
316
311
  configured_key = (
317
312
  os.getenv('MICROSYS_TOTP_SECRET_KEY')
@@ -321,18 +316,18 @@ def _totp_secret_seed():
321
316
  )
322
317
  return str(configured_key or 'microsys-totp-secret-dev-key')
323
318
 
324
-
319
+ # Get Fernet instance for TOTP encryption
325
320
  def _totp_fernet():
326
321
  from cryptography.fernet import Fernet
327
322
 
328
323
  digest = hashlib.sha256(_totp_secret_seed().encode('utf-8')).digest()
329
324
  return Fernet(base64.urlsafe_b64encode(digest))
330
325
 
331
-
326
+ # Check if TOTP secret is encrypted
332
327
  def is_encrypted_totp_secret(value):
333
328
  return isinstance(value, str) and value.startswith(TOTP_SECRET_PREFIX)
334
329
 
335
-
330
+ # Encrypt TOTP secret using Fernet
336
331
  def encrypt_totp_secret(raw_secret):
337
332
  raw_secret = str(raw_secret or '').strip()
338
333
  if not raw_secret:
@@ -342,7 +337,7 @@ def encrypt_totp_secret(raw_secret):
342
337
  encrypted = _totp_fernet().encrypt(raw_secret.encode('utf-8')).decode('utf-8')
343
338
  return f'{TOTP_SECRET_PREFIX}{encrypted}'
344
339
 
345
-
340
+ # Decrypt TOTP secret using Fernet
346
341
  def decrypt_totp_secret(stored_secret):
347
342
  stored_secret = str(stored_secret or '').strip()
348
343
  if not stored_secret:
@@ -355,11 +350,11 @@ def decrypt_totp_secret(stored_secret):
355
350
  except Exception:
356
351
  return ''
357
352
 
358
-
353
+ # Get decrypted TOTP secret from profile
359
354
  def get_profile_totp_secret(profile):
360
355
  return decrypt_totp_secret(getattr(profile, 'totp_secret', ''))
361
356
 
362
-
357
+ # Update profile TOTP state without full profile save
363
358
  def set_profile_totp_state(profile, *, raw_secret=_UNSET, enabled=_UNSET):
364
359
  if profile is None or not getattr(profile, 'pk', None):
365
360
  raise ValueError('Profile must be saved before updating TOTP state.')
@@ -377,7 +372,7 @@ def set_profile_totp_state(profile, *, raw_secret=_UNSET, enabled=_UNSET):
377
372
  setattr(profile, field_name, value)
378
373
  return profile
379
374
 
380
-
375
+ # Get effective email configuration from settings or database
381
376
  def get_microsys_email_config(*, include_secret=False):
382
377
  try:
383
378
  SystemSettings = apps.get_model('microsys', 'SystemSettings')
@@ -438,7 +433,7 @@ def get_microsys_email_config(*, include_secret=False):
438
433
  'ui_hints': stored_hints,
439
434
  }
440
435
 
441
-
436
+ # Get email service status
442
437
  def get_email_service_status():
443
438
  """
444
439
  Report whether Microsys-owned email flows are configured without touching
@@ -545,7 +540,7 @@ def get_email_service_status():
545
540
  'reason': 'custom_backend_configured' if configured else 'missing_default_from_email',
546
541
  }
547
542
 
548
-
543
+ # Send Microsys-owned transactional email
549
544
  def send_microsys_mail(subject, message, recipient_list, *, from_email=None, fail_silently=False):
550
545
  """Send Microsys-owned transactional email through the selected delivery path."""
551
546
  email_config = get_microsys_email_config(include_secret=True)
@@ -598,6 +593,7 @@ def is_sensitive_activity_field_name(field_name):
598
593
  return True
599
594
  return False
600
595
 
596
+
601
597
  def microsys_settings(scope):
602
598
  """
603
599
  Apply the default MicroSys settings requirements to a Django settings module.
@@ -706,6 +702,7 @@ def microsys_settings(scope):
706
702
  scope["FORMAT_MODULE_PATH"] = [format_module_path, "microsys.formats"]
707
703
  return scope
708
704
 
705
+
709
706
  def _normalize_asset_url(value, fallback_base='/media/'):
710
707
  """Ensure stored media paths render as browser-safe absolute URLs."""
711
708
  if not value:
@@ -888,7 +885,6 @@ def can_manage_target_user(actor, target_user=None):
888
885
  target_scope = get_user_scope(target_user)
889
886
  if actor_scope and actor_scope != target_scope:
890
887
  return False
891
-
892
888
  return True
893
889
 
894
890
 
@@ -933,7 +929,7 @@ def is_central_staff(user):
933
929
  return False
934
930
  return not user.has_perm('microsys.manage_scopes')
935
931
 
936
-
932
+ # Normalize permission codenames to a set
937
933
  def _normalize_permission_codename_set(permission_codenames):
938
934
  normalized = set()
939
935
  for permission in permission_codenames or []:
@@ -1086,7 +1082,7 @@ def get_user_management_tier_state(
1086
1082
  })
1087
1083
  return tier_state
1088
1084
 
1089
-
1085
+ # Get user management tier state for a specific user
1090
1086
  def get_user_management_tier_state_for_user(user):
1091
1087
  if not user or not getattr(user, 'is_authenticated', False):
1092
1088
  return get_user_management_tier_state(
@@ -1143,7 +1139,7 @@ def user_can_view_activity_log(user):
1143
1139
  return True
1144
1140
  return user.has_perm('microsys.view_activitylog') or user.has_perm('microsys.view_activity_log')
1145
1141
 
1146
-
1142
+ # Check if user has section view permission
1147
1143
  def user_has_section_view_permission(user):
1148
1144
  if not user or not getattr(user, 'is_authenticated', False):
1149
1145
  return False
@@ -1152,6 +1148,7 @@ def user_has_section_view_permission(user):
1152
1148
  return user.has_perm('microsys.view_sections') or user.has_perm('microsys.manage_sections')
1153
1149
 
1154
1150
 
1151
+ # Check if user has section manage permission
1155
1152
  def user_has_section_manage_permission(user):
1156
1153
  if not user or not getattr(user, 'is_authenticated', False):
1157
1154
  return False
@@ -1216,7 +1213,6 @@ def user_has_any_permission_tokens(user, permissions, default_visible_to_all=Fal
1216
1213
  permissions = [permissions]
1217
1214
  return any(user_matches_permission_token(user, p) for p in permissions)
1218
1215
 
1219
-
1220
1216
  # Activity Logging — Universal logging utility for user actions
1221
1217
  def log_user_action(request, action, instance=None, model_name=None, details=None, number=None, object_id=None):
1222
1218
  """
@@ -1282,7 +1278,7 @@ DEFAULT_LANGUAGE_CATALOG = {
1282
1278
  'ar': {'name': 'العربية', 'dir': 'rtl', 'flag': '🇱🇾'},
1283
1279
  }
1284
1280
 
1285
-
1281
+ # Normalize language code to standard format
1286
1282
  def _normalize_language_code(code):
1287
1283
  normalized = str(code or '').strip().lower().replace('_', '-')
1288
1284
  if not normalized:
@@ -1320,7 +1316,7 @@ def normalize_language_catalog(*layers):
1320
1316
  }
1321
1317
  return merged
1322
1318
 
1323
-
1319
+ # Normalize system names dictionary
1324
1320
  def normalize_system_names(value):
1325
1321
  names = {}
1326
1322
  if isinstance(value, dict):
@@ -1331,7 +1327,7 @@ def normalize_system_names(value):
1331
1327
  names[code] = name
1332
1328
  return names
1333
1329
 
1334
-
1330
+ # Resolve system name for a given language
1335
1331
  def resolve_system_name(system_names, lang_code=None, default_language='en'):
1336
1332
  names = normalize_system_names(system_names)
1337
1333
  lang = _normalize_language_code(lang_code) or _normalize_language_code(default_language) or 'en'
@@ -1344,7 +1340,7 @@ def resolve_system_name(system_names, lang_code=None, default_language='en'):
1344
1340
  return value
1345
1341
  return 'microSYS'
1346
1342
 
1347
-
1343
+ # Build grouped configuration structure
1348
1344
  def build_config_groups(config, current_language=None):
1349
1345
  languages = normalize_language_catalog(config.get('languages', {}))
1350
1346
  default_language = _normalize_language_code(config.get('default_language')) or 'en'
@@ -1413,7 +1409,7 @@ def _dedupe_sidebar_entries(entries):
1413
1409
  deduped.append(entry)
1414
1410
  return deduped
1415
1411
 
1416
-
1412
+ # Return default titlebar configuration
1417
1413
  def default_titlebar_config():
1418
1414
  return {
1419
1415
  'show_title': True,
@@ -1427,7 +1423,7 @@ def default_titlebar_config():
1427
1423
  'surface': 'default',
1428
1424
  }
1429
1425
 
1430
-
1426
+ # Normalize and validate titlebar configuration
1431
1427
  def normalize_titlebar_config(titlebar_config):
1432
1428
  config = titlebar_config if isinstance(titlebar_config, dict) else {}
1433
1429
  normalized = default_titlebar_config()
@@ -1463,7 +1459,7 @@ def normalize_titlebar_config(titlebar_config):
1463
1459
 
1464
1460
  return normalized
1465
1461
 
1466
-
1462
+ # Return default sidebar configuration
1467
1463
  def default_sidebar_config():
1468
1464
  return {
1469
1465
  'enabled': True,
@@ -1477,7 +1473,7 @@ def default_sidebar_config():
1477
1473
  'collapse_mode': DEFAULT_SIDEBAR_COLLAPSE_MODE,
1478
1474
  }
1479
1475
 
1480
-
1476
+ # Normalize and validate sidebar behavior configuration
1481
1477
  def normalize_sidebar_behavior(sidebar_config):
1482
1478
  config = sidebar_config if isinstance(sidebar_config, dict) else {}
1483
1479
  normalized = default_sidebar_config()
@@ -1508,13 +1504,13 @@ def normalize_sidebar_behavior(sidebar_config):
1508
1504
 
1509
1505
  return normalized
1510
1506
 
1511
-
1507
+ # Get effective allowed themes from configuration
1512
1508
  def get_effective_allowed_themes(config):
1513
1509
  if not isinstance(config, dict):
1514
1510
  return tuple(normalize_allowed_themes())
1515
1511
  return tuple(normalize_allowed_themes(config.get('allowed_themes')))
1516
1512
 
1517
-
1513
+ # Resolve user theme preference against allowed themes
1518
1514
  def resolve_user_theme_preference(user_prefs, config):
1519
1515
  prefs = dict(user_prefs or {})
1520
1516
  allowed_themes = set(get_effective_allowed_themes(config))
@@ -1531,7 +1527,7 @@ def resolve_user_theme_preference(user_prefs, config):
1531
1527
  prefs['theme'] = default_theme
1532
1528
  return prefs
1533
1529
 
1534
-
1530
+ # Resolve user sidebar density preference
1535
1531
  def resolve_sidebar_density_preference(user_prefs, config):
1536
1532
  prefs = dict(user_prefs or {})
1537
1533
  sidebar_config = normalize_sidebar_behavior(config.get('sidebar', {}))
@@ -1544,7 +1540,7 @@ def resolve_sidebar_density_preference(user_prefs, config):
1544
1540
  prefs['sidebar_density'] = sidebar_config.get('density', DEFAULT_SIDEBAR_DENSITY)
1545
1541
  return prefs
1546
1542
 
1547
-
1543
+ # Resolve user sidebar collapsed preference
1548
1544
  def resolve_sidebar_collapsed_preference(user_prefs, config, session_collapsed=False):
1549
1545
  prefs = dict(user_prefs or {})
1550
1546
  collapse_mode = normalize_sidebar_behavior(config.get('sidebar', {})).get('collapse_mode', DEFAULT_SIDEBAR_COLLAPSE_MODE)
@@ -1557,11 +1553,9 @@ def resolve_sidebar_collapsed_preference(user_prefs, config, session_collapsed=F
1557
1553
  raw_value = raw_value.lower() == 'true'
1558
1554
  return bool(raw_value), prefs
1559
1555
 
1560
-
1561
1556
  SYSTEM_SETTINGS_EXPORT_FORMAT = 'django-microsys.system-settings'
1562
1557
  SYSTEM_SETTINGS_EXPORT_VERSION = 1
1563
1558
 
1564
-
1565
1559
  SYSTEM_SETTINGS_EXPORT_FIELDS = (
1566
1560
  'system_names',
1567
1561
  'logo',
@@ -1588,13 +1582,13 @@ SYSTEM_SETTINGS_EXPORT_FIELDS = (
1588
1582
  'titlebar_config',
1589
1583
  )
1590
1584
 
1591
-
1585
+ # Extract filename from FieldFile or string
1592
1586
  def _field_file_name(value):
1593
1587
  if isinstance(value, FieldFile):
1594
1588
  return value.name or ''
1595
1589
  return str(value or '')
1596
1590
 
1597
-
1591
+ # Coerce value to boolean for import settings
1598
1592
  def _coerce_import_bool(value):
1599
1593
  if isinstance(value, str):
1600
1594
  return value.strip().lower() in {'1', 'true', 'yes', 'on'}
@@ -1695,7 +1689,6 @@ def normalize_system_settings_import_payload(payload):
1695
1689
  ):
1696
1690
  if bool_field in normalized:
1697
1691
  normalized[bool_field] = _coerce_import_bool(normalized[bool_field])
1698
-
1699
1692
  return normalized
1700
1693
 
1701
1694
 
@@ -2467,7 +2460,7 @@ def collect_related_objects(instance):
2467
2460
 
2468
2461
  return related_data
2469
2462
 
2470
-
2463
+ # Generate detail context from model instance
2471
2464
  def _build_generic_detail_context(instance, request=None):
2472
2465
  """
2473
2466
  Dynamically generates a list of {'label': ..., 'value': ...} dictionaries
@@ -2532,7 +2525,7 @@ def _build_generic_detail_context(instance, request=None):
2532
2525
 
2533
2526
  return fields_data
2534
2527
 
2535
-
2528
+ # Resolve translated display label for detail views
2536
2529
  def resolve_detail_field_label(instance, field, request=None, strings=None):
2537
2530
  """Resolve a translated display label for generic detail views."""
2538
2531
  s = strings or get_strings(get_current_language_code(request))
@@ -2563,7 +2556,6 @@ def resolve_detail_field_label(instance, field, request=None, strings=None):
2563
2556
 
2564
2557
  return raw_label or field_name
2565
2558
 
2566
-
2567
2559
  # Dynamic Table Builder — Generates a django-tables2 Table class at runtime
2568
2560
  def _build_generic_table_class(model):
2569
2561
  """
@@ -3127,7 +3119,6 @@ def toggle_sidebar(request):
3127
3119
  return JsonResponse({"status": "success"})
3128
3120
  return JsonResponse({"status": "error"}, status=400)
3129
3121
 
3130
-
3131
3122
  # Form Helper — Applies shared field classes, direction, and optional inline labels
3132
3123
  def set_field_attrs(form, request=None, inline_labels=False):
3133
3124
  """Set common attributes for all fields in the form."""
@@ -3260,7 +3251,7 @@ def set_field_attrs(form, request=None, inline_labels=False):
3260
3251
  if is_date:
3261
3252
  field.widget.attrs['autocomplete'] = 'off'
3262
3253
 
3263
-
3254
+ # Set up modern Crispy layout for django-filter FilterSet
3264
3255
  def setup_filter_helper(filter_instance, request=None, preserve_keys=None, inline_labels=True):
3265
3256
  """
3266
3257
  Sets up a modern, responsive Crispy layout for a django-filter FilterSet.
@@ -3333,7 +3324,7 @@ def setup_filter_helper(filter_instance, request=None, preserve_keys=None, inlin
3333
3324
  # Apply shared field attrs, defaulting filters to inline placeholder labels.
3334
3325
  set_field_attrs(filter_instance.form, request, inline_labels=inline_labels)
3335
3326
 
3336
-
3327
+ # Build advanced filter helper with expandable fields
3337
3328
  def advanced_filter_helper(filter_instance, config=None, request=None, preserve_keys=None, inline_labels=True):
3338
3329
  """
3339
3330
  Build an "advanced" filter helper with:
@@ -3647,7 +3638,7 @@ def advanced_filter_helper(filter_instance, config=None, request=None, preserve_
3647
3638
  helper.layout = Layout(*layout_items)
3648
3639
  filter_instance.form.helper = helper
3649
3640
 
3650
- # Form Helper — Renames the first choice in a Selection menu
3641
+ # Form Helper — Set the first choice of a selection field safely
3651
3642
  def set_first_choice(field, placeholder):
3652
3643
  """Set the first choice of a specified field safely without overwriting data."""
3653
3644
  # 1. Handle fields with explicit empty_label (ModelChoiceField, etc.)
@@ -3681,7 +3672,6 @@ def set_first_choice(field, placeholder):
3681
3672
 
3682
3673
  field.choices = choices
3683
3674
 
3684
-
3685
3675
  # Form Helper — Translates a choices list using MS_TRANS choice_ prefix
3686
3676
  def translate_choices(choices, ms_trans):
3687
3677
  """
@@ -3697,7 +3687,6 @@ def translate_choices(choices, ms_trans):
3697
3687
  translated.append((value, ms_trans.get(f'choice_{value}', label)))
3698
3688
  return translated
3699
3689
 
3700
-
3701
3690
  # Form Helper — Detects if a Crispy form layout already contains Submit/Button elements
3702
3691
  def has_submit_button(form):
3703
3692
  """
@@ -3747,3 +3736,17 @@ def has_submit_button(form):
3747
3736
  return True
3748
3737
 
3749
3738
  return False
3739
+
3740
+ # Return app version from VERSION file at the parent caller's folder
3741
+ def get_app_version(calling_file_path: str) -> str:
3742
+ """
3743
+ Reads the VERSION file from the same directory as the calling file.
3744
+ Usage: VERSION = get_app_version(__file__)
3745
+ """
3746
+ try:
3747
+ # Resolves the directory of the file that called this function
3748
+ app_dir = Path(calling_file_path).resolve().parent
3749
+ with open(app_dir / "VERSION", "r") as f:
3750
+ return f.read().strip()
3751
+ except FileNotFoundError:
3752
+ return "unknown"
@@ -1 +0,0 @@
1
- 2.2.2
File without changes