django-microsys 2.1.9__tar.gz → 2.2.1__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.1.9 → django_microsys-2.2.1}/PKG-INFO +1 -1
  2. {django_microsys-2.1.9 → django_microsys-2.2.1}/django_microsys.egg-info/PKG-INFO +1 -1
  3. {django_microsys-2.1.9 → django_microsys-2.2.1}/django_microsys.egg-info/SOURCES.txt +12 -4
  4. django_microsys-2.2.1/microsys/VERSION +1 -0
  5. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/context_processors.py +29 -2
  6. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/fetcher.py +21 -4
  7. django_microsys-2.2.1/microsys/fonts.py +72 -0
  8. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/forms.py +157 -36
  9. django_microsys-2.2.1/microsys/migrations/0005_systemsettings_allow_user_font_override_and_more.py +29 -0
  10. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/models.py +17 -17
  11. django_microsys-2.2.1/microsys/static/microsys/fonts/cairo-bold.woff2 +0 -0
  12. django_microsys-2.2.1/microsys/static/microsys/fonts/cairo-medium.woff2 +0 -0
  13. django_microsys-2.2.1/microsys/static/microsys/fonts/cairo-regular.woff2 +0 -0
  14. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/language/css/main.css +1 -1
  15. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/main.css +17 -25
  16. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/system_setup.css +55 -0
  17. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/js/base_head.js +7 -0
  18. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/js/base_runtime.js +1 -2
  19. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/js/options.js +21 -0
  20. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/js/system_setup.js +64 -1
  21. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/base.html +9 -3
  22. django_microsys-2.2.1/microsys/templates/microsys/includes/font_previews.html +12 -0
  23. django_microsys-2.2.1/microsys/templates/microsys/includes/font_settings_matrix.html +46 -0
  24. django_microsys-2.2.1/microsys/templates/microsys/includes/language_fonts_editor.html +36 -0
  25. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/options.html +25 -1
  26. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/tables/table.html +1 -1
  27. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templatetags/microsys_tags.py +11 -0
  28. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/translations.py +47 -15
  29. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/utils.py +78 -8
  30. django_microsys-2.1.9/microsys/VERSION +0 -1
  31. {django_microsys-2.1.9 → django_microsys-2.2.1}/LICENSE +0 -0
  32. {django_microsys-2.1.9 → django_microsys-2.2.1}/MANIFEST.in +0 -0
  33. {django_microsys-2.1.9 → django_microsys-2.2.1}/README.md +0 -0
  34. {django_microsys-2.1.9 → django_microsys-2.2.1}/django_microsys.egg-info/dependency_links.txt +0 -0
  35. {django_microsys-2.1.9 → django_microsys-2.2.1}/django_microsys.egg-info/entry_points.txt +0 -0
  36. {django_microsys-2.1.9 → django_microsys-2.2.1}/django_microsys.egg-info/requires.txt +0 -0
  37. {django_microsys-2.1.9 → django_microsys-2.2.1}/django_microsys.egg-info/top_level.txt +0 -0
  38. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/__init__.py +0 -0
  39. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/__main__.py +0 -0
  40. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/admin.py +0 -0
  41. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/api.py +0 -0
  42. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/apps.py +0 -0
  43. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/cli.py +0 -0
  44. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/constants.py +0 -0
  45. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/discovery.py +0 -0
  46. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/filters.py +0 -0
  47. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/formats/__init__.py +0 -0
  48. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/formats/ar/__init__.py +0 -0
  49. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/formats/ar/formats.py +0 -0
  50. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/formats/en/__init__.py +0 -0
  51. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/formats/en/formats.py +0 -0
  52. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/guards.py +0 -0
  53. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/management/commands/__init__.py +0 -0
  54. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/management/commands/microsys_check.py +0 -0
  55. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/management/commands/microsys_setup.py +0 -0
  56. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/management/commands/migrator.py +0 -0
  57. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/management/commands/seed_activity_log.py +0 -0
  58. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/managers.py +0 -0
  59. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/middleware.py +0 -0
  60. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/migrations/0001_initial.py +0 -0
  61. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/migrations/0002_public_registration.py +0 -0
  62. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/migrations/0003_public_root_split.py +0 -0
  63. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/migrations/0004_client_ip_and_trusted_devices.py +0 -0
  64. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/migrations/__init__.py +0 -0
  65. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/patches.py +0 -0
  66. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/registration.py +0 -0
  67. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold.py +0 -0
  68. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/README.md.tmpl +0 -0
  69. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/__init__.py.tmpl +0 -0
  70. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/apps.py.tmpl +0 -0
  71. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/filters.py.tmpl +0 -0
  72. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/forms.py.tmpl +0 -0
  73. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/migrations/__init__.py.tmpl +0 -0
  74. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/models.py.tmpl +0 -0
  75. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/tables.py.tmpl +0 -0
  76. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/templates/example_record_confirm_delete.html.tmpl +0 -0
  77. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/templates/example_record_detail.html.tmpl +0 -0
  78. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/templates/example_record_form.html.tmpl +0 -0
  79. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/templates/example_record_list.html.tmpl +0 -0
  80. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/tests/__init__.py.tmpl +0 -0
  81. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/tests/test_app.py.tmpl +0 -0
  82. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/translations.py.tmpl +0 -0
  83. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/urls.py.tmpl +0 -0
  84. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/app/views.py.tmpl +0 -0
  85. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/.nginx/nginx.conf.tmpl +0 -0
  86. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/.secrets/.env.tmpl +0 -0
  87. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/Dockerfile.tmpl +0 -0
  88. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/README.md.tmpl +0 -0
  89. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/compose.dev.yml.tmpl +0 -0
  90. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/compose.yml.tmpl +0 -0
  91. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/dockerignore.tmpl +0 -0
  92. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/docs/README.md.tmpl +0 -0
  93. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/entrypoint.sh.tmpl +0 -0
  94. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/gitattributes.tmpl +0 -0
  95. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/gitignore.tmpl +0 -0
  96. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/gunicorn.py.tmpl +0 -0
  97. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/manage.py.tmpl +0 -0
  98. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/package/__init__.py.tmpl +0 -0
  99. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/package/asgi.py.tmpl +0 -0
  100. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/package/celery.py.tmpl +0 -0
  101. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/package/settings.py.tmpl +0 -0
  102. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/package/urls.py.tmpl +0 -0
  103. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/package/wsgi.py.tmpl +0 -0
  104. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/req.txt.tmpl +0 -0
  105. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/start.ps1.tmpl +0 -0
  106. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/start.sh.tmpl +0 -0
  107. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/tests/__init__.py.tmpl +0 -0
  108. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/tests/test_scaffold.py.tmpl +0 -0
  109. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/scaffold_templates/project/tools/smtp_relay.py.tmpl +0 -0
  110. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/signals.py +0 -0
  111. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap-icons.css +0 -0
  112. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap-icons.woff +0 -0
  113. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap-icons.woff2 +0 -0
  114. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap.bundle.min.js +0 -0
  115. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap.bundle.min.js.map +0 -0
  116. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap.min.css +0 -0
  117. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap.min.css.map +0 -0
  118. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap.rtl.min.css +0 -0
  119. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/bootstrap/bootstrap.rtl.min.css.map +0 -0
  120. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/favicon.ico +0 -0
  121. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/img/base_logo.webp +0 -0
  122. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/img/default_profile.webp +0 -0
  123. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/img/login_logo.webp +0 -0
  124. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/accessibility/css/main.css +0 -0
  125. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/accessibility/js/main.js +0 -0
  126. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/activitylog/js/main.js +0 -0
  127. {django_microsys-2.1.9/microsys/static/microsys/language → django_microsys-2.2.1/microsys/static/microsys}/fonts/TwemojiCountryFlags.woff2 +0 -0
  128. /django_microsys-2.1.9/microsys/static/microsys/main/fonts/Shabwa-Bold.woff2 → /django_microsys-2.2.1/microsys/static/microsys/fonts/shabwa-bold.woff2 +0 -0
  129. /django_microsys-2.1.9/microsys/static/microsys/main/fonts/Shabwa-Medium.woff2 → /django_microsys-2.2.1/microsys/static/microsys/fonts/shabwa-medium.woff2 +0 -0
  130. /django_microsys-2.1.9/microsys/static/microsys/main/fonts/Shabwa.woff2 → /django_microsys-2.2.1/microsys/static/microsys/fonts/shabwa-regular.woff2 +0 -0
  131. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/forms/css/file_field.css +0 -0
  132. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/forms/css/form_actions.css +0 -0
  133. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/forms/css/form_fields.css +0 -0
  134. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/forms/js/file_field.js +0 -0
  135. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/forms/js/filter_form.js +0 -0
  136. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/autofill/js/main.js +0 -0
  137. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/context_menu/css/main.css +0 -0
  138. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/context_menu/js/main.js +0 -0
  139. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/context_menu/js/section_manager.js +0 -0
  140. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/dynamic_modal/js/main.js +0 -0
  141. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/prevent_double_submit.js +0 -0
  142. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/scan_link/js/main.js +0 -0
  143. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/scan_link/js/scan_button.js +0 -0
  144. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/helpers/wizard/js/main.js +0 -0
  145. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/language/js/main.js +0 -0
  146. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/language/js/translations.js +0 -0
  147. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/buttons.css +0 -0
  148. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/dropdowns.css +0 -0
  149. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/index_cards.css +0 -0
  150. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/options.css +0 -0
  151. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/pagination.css +0 -0
  152. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/selectors.css +0 -0
  153. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/tables.css +0 -0
  154. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/template_cleanup.css +0 -0
  155. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/css/titlebar.css +0 -0
  156. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/js/selectors.js +0 -0
  157. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/main/js/tables.js +0 -0
  158. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/sections/js/manage_sections.js +0 -0
  159. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/sidebar/css/main.css +0 -0
  160. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/sidebar/css/reorder.css +0 -0
  161. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/sidebar/css/theme_picker.css +0 -0
  162. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/sidebar/js/main.js +0 -0
  163. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/sidebar/js/preload.js +0 -0
  164. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/sidebar/js/reorder.js +0 -0
  165. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/sidebar/js/theme_picker.js +0 -0
  166. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/blue.css +0 -0
  167. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/dark.css +0 -0
  168. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/gold.css +0 -0
  169. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/gothic.css +0 -0
  170. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/green.css +0 -0
  171. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/light.css +0 -0
  172. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/mono.css +0 -0
  173. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/neon.css +0 -0
  174. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/red.css +0 -0
  175. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/css/retro.css +0 -0
  176. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/themes/js/main.js +0 -0
  177. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/tutorial/css/main.css +0 -0
  178. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/tutorial/js/driver.js +0 -0
  179. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/tutorial/js/main.js +0 -0
  180. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/css/login.css +0 -0
  181. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/css/permissions.css +0 -0
  182. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/css/profile.css +0 -0
  183. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/css/user_hub.css +0 -0
  184. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/js/login.js +0 -0
  185. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/js/manage_users.js +0 -0
  186. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/js/permissions.js +0 -0
  187. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/js/profile_2fa.js +0 -0
  188. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/js/profile_image_widget.js +0 -0
  189. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/js/twofa_verify.js +0 -0
  190. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/microsys/users/js/user_hub.js +0 -0
  191. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/vanillajs-datepicker/datepicker-bs5.min.css +0 -0
  192. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/static/vanillajs-datepicker/datepicker.min.js +0 -0
  193. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/tables.py +0 -0
  194. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/bootstrap5/layout/field_file.html +0 -0
  195. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/2fa/verify.html +0 -0
  196. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/activitylog/activity_log.html +0 -0
  197. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/activitylog/activity_log_detail_modal.html +0 -0
  198. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/form_base.html +0 -0
  199. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/forms/assets_head.html +0 -0
  200. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/forms/assets_scripts.html +0 -0
  201. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/forms/crispy_file_field.html +0 -0
  202. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/forms/file_input.html +0 -0
  203. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/forms/filter_assets_head.html +0 -0
  204. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/forms/filter_assets_scripts.html +0 -0
  205. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/helpers/dynamic_modal.html +0 -0
  206. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/helpers/dynamic_modal_combined.html +0 -0
  207. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/helpers/dynamic_modal_detail.html +0 -0
  208. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/helpers/dynamic_modal_form.html +0 -0
  209. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/helpers/dynamic_modal_list.html +0 -0
  210. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/helpers/micro_context_menu.html +0 -0
  211. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/language_catalog_editor.html +0 -0
  212. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/language_previews.html +0 -0
  213. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/sidebar_builder.html +0 -0
  214. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/sidebar_density_previews.html +0 -0
  215. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/sidebar_items.html +0 -0
  216. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/system_names_editor.html +0 -0
  217. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/system_setup.html +0 -0
  218. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/table_density_previews.html +0 -0
  219. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/theme_previews.html +0 -0
  220. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/theme_settings_matrix.html +0 -0
  221. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/titlebar.html +0 -0
  222. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/translation_matrix_editor.html +0 -0
  223. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/includes/tutorial.html +0 -0
  224. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/list_base.html +0 -0
  225. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/registration/pending.html +0 -0
  226. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/scopes/scope_actions.html +0 -0
  227. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/scopes/scope_form.html +0 -0
  228. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/scopes/scope_manager.html +0 -0
  229. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/sections/manage_sections.html +0 -0
  230. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/sections/subsection_select.html +0 -0
  231. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/sidebar/auto.html +0 -0
  232. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/sidebar/extra_groups.html +0 -0
  233. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/sidebar/main.html +0 -0
  234. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/sidebar/tree.html +0 -0
  235. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/users/grouped_permissions.html +0 -0
  236. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/users/manage_users.html +0 -0
  237. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/users/profile.html +0 -0
  238. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/users/profile_image_widget.html +0 -0
  239. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/users/user_detail_modal.html +0 -0
  240. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/microsys/users/user_hub.html +0 -0
  241. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/registration/email/verify_registration.txt +0 -0
  242. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/registration/login.html +0 -0
  243. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/registration/register.html +0 -0
  244. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/registration/register_sent.html +0 -0
  245. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templates/registration/register_verify.html +0 -0
  246. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templatetags/__init__.py +0 -0
  247. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templatetags/microsys_translation.py +0 -0
  248. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/templatetags/sidebar_tags.py +0 -0
  249. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/themes.py +0 -0
  250. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/urls.py +0 -0
  251. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/__init__.py +0 -0
  252. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/activitylog.py +0 -0
  253. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/general.py +0 -0
  254. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/profile.py +0 -0
  255. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/registration.py +0 -0
  256. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/scopes.py +0 -0
  257. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/sections.py +0 -0
  258. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/sidebar.py +0 -0
  259. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/twofa.py +0 -0
  260. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/views/users.py +0 -0
  261. {django_microsys-2.1.9 → django_microsys-2.2.1}/microsys/widgets.py +0 -0
  262. {django_microsys-2.1.9 → django_microsys-2.2.1}/pyproject.toml +0 -0
  263. {django_microsys-2.1.9 → django_microsys-2.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django_microsys
3
- Version: 2.1.9
3
+ Version: 2.2.1
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.1.9
3
+ Version: 2.2.1
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
@@ -20,6 +20,7 @@ microsys/context_processors.py
20
20
  microsys/discovery.py
21
21
  microsys/fetcher.py
22
22
  microsys/filters.py
23
+ microsys/fonts.py
23
24
  microsys/forms.py
24
25
  microsys/guards.py
25
26
  microsys/managers.py
@@ -49,6 +50,7 @@ microsys/migrations/0001_initial.py
49
50
  microsys/migrations/0002_public_registration.py
50
51
  microsys/migrations/0003_public_root_split.py
51
52
  microsys/migrations/0004_client_ip_and_trusted_devices.py
53
+ microsys/migrations/0005_systemsettings_allow_user_font_override_and_more.py
52
54
  microsys/migrations/__init__.py
53
55
  microsys/scaffold_templates/app/README.md.tmpl
54
56
  microsys/scaffold_templates/app/__init__.py.tmpl
@@ -108,6 +110,13 @@ microsys/static/img/login_logo.webp
108
110
  microsys/static/microsys/accessibility/css/main.css
109
111
  microsys/static/microsys/accessibility/js/main.js
110
112
  microsys/static/microsys/activitylog/js/main.js
113
+ microsys/static/microsys/fonts/TwemojiCountryFlags.woff2
114
+ microsys/static/microsys/fonts/cairo-bold.woff2
115
+ microsys/static/microsys/fonts/cairo-medium.woff2
116
+ microsys/static/microsys/fonts/cairo-regular.woff2
117
+ microsys/static/microsys/fonts/shabwa-bold.woff2
118
+ microsys/static/microsys/fonts/shabwa-medium.woff2
119
+ microsys/static/microsys/fonts/shabwa-regular.woff2
111
120
  microsys/static/microsys/forms/css/file_field.css
112
121
  microsys/static/microsys/forms/css/form_actions.css
113
122
  microsys/static/microsys/forms/css/form_fields.css
@@ -123,7 +132,6 @@ microsys/static/microsys/helpers/scan_link/js/main.js
123
132
  microsys/static/microsys/helpers/scan_link/js/scan_button.js
124
133
  microsys/static/microsys/helpers/wizard/js/main.js
125
134
  microsys/static/microsys/language/css/main.css
126
- microsys/static/microsys/language/fonts/TwemojiCountryFlags.woff2
127
135
  microsys/static/microsys/language/js/main.js
128
136
  microsys/static/microsys/language/js/translations.js
129
137
  microsys/static/microsys/main/css/buttons.css
@@ -137,9 +145,6 @@ microsys/static/microsys/main/css/system_setup.css
137
145
  microsys/static/microsys/main/css/tables.css
138
146
  microsys/static/microsys/main/css/template_cleanup.css
139
147
  microsys/static/microsys/main/css/titlebar.css
140
- microsys/static/microsys/main/fonts/Shabwa-Bold.woff2
141
- microsys/static/microsys/main/fonts/Shabwa-Medium.woff2
142
- microsys/static/microsys/main/fonts/Shabwa.woff2
143
148
  microsys/static/microsys/main/js/base_head.js
144
149
  microsys/static/microsys/main/js/base_runtime.js
145
150
  microsys/static/microsys/main/js/options.js
@@ -200,7 +205,10 @@ microsys/templates/microsys/helpers/dynamic_modal_detail.html
200
205
  microsys/templates/microsys/helpers/dynamic_modal_form.html
201
206
  microsys/templates/microsys/helpers/dynamic_modal_list.html
202
207
  microsys/templates/microsys/helpers/micro_context_menu.html
208
+ microsys/templates/microsys/includes/font_previews.html
209
+ microsys/templates/microsys/includes/font_settings_matrix.html
203
210
  microsys/templates/microsys/includes/language_catalog_editor.html
211
+ microsys/templates/microsys/includes/language_fonts_editor.html
204
212
  microsys/templates/microsys/includes/language_previews.html
205
213
  microsys/templates/microsys/includes/options.html
206
214
  microsys/templates/microsys/includes/sidebar_builder.html
@@ -0,0 +1 @@
1
+ 2.2.1
@@ -177,7 +177,7 @@ def microsys_context(request):
177
177
  context = {}
178
178
 
179
179
  # 1. Branding / App Config
180
- from .utils import build_config_groups, get_system_config
180
+ from .utils import build_config_groups, get_system_config, normalize_allowed_fonts
181
181
  final_config = get_system_config()
182
182
 
183
183
  # 4. Language / i18n (resolved BEFORE branding overrides so we know current_lang)
@@ -342,7 +342,34 @@ def microsys_context(request):
342
342
  final_config,
343
343
  context['titlebar'],
344
344
  )
345
-
345
+ # 8. Font Resolution
346
+ from .fonts import generate_font_face_css, get_builtin_fonts
347
+ allowed_fonts = normalize_allowed_fonts(final_config.get('allowed_fonts'))
348
+ context['font_face_css'] = generate_font_face_css(allowed_fonts)
349
+
350
+ allow_user_font_override = bool(final_config.get('allow_user_font_override', True))
351
+ default_fonts_by_lang = final_config.get('default_fonts', {})
352
+
353
+ active_font = None
354
+ if allow_user_font_override:
355
+ active_font = user_prefs.get('font')
356
+
357
+ if not active_font or active_font not in allowed_fonts:
358
+ active_font = default_fonts_by_lang.get(current_lang)
359
+
360
+ if not active_font or active_font not in allowed_fonts:
361
+ # Global fallback based on current language
362
+ active_font = 'shabwa' if current_lang == 'ar' else 'cairo'
363
+
364
+ # Ensure active_font is valid, else hard fallback to shabwa
365
+ if active_font not in allowed_fonts and allowed_fonts:
366
+ active_font = allowed_fonts[0]
367
+ elif not active_font:
368
+ active_font = 'shabwa'
369
+
370
+ context['active_font'] = active_font
371
+ context['font_picker_enabled'] = bool(allow_user_font_override and len(allowed_fonts) > 1)
372
+
346
373
  return context
347
374
 
348
375
  def clear_sidebar_cache():
@@ -4,12 +4,29 @@ from django.http import HttpResponse, HttpResponseRedirect
4
4
  from django.db.models.query import QuerySet
5
5
  from django.contrib import messages
6
6
  from django.db.models import FileField
7
+ from django.utils.http import url_has_allowed_host_and_scheme
7
8
  from io import BytesIO
8
9
  import mimetypes
9
10
  import openpyxl
10
11
  import zipfile
11
12
  from .utils import log_user_action
12
13
 
14
+ def _safe_referer(request, fallback='/'):
15
+ referer = request.META.get('HTTP_REFERER')
16
+ if referer:
17
+ from django.conf import settings
18
+ allowed_hosts = {request.get_host()}
19
+ if hasattr(settings, 'ALLOWED_HOSTS') and settings.ALLOWED_HOSTS:
20
+ for host in settings.ALLOWED_HOSTS:
21
+ if host:
22
+ if host.startswith('.'):
23
+ allowed_hosts.add(host[1:])
24
+ elif host != '*':
25
+ allowed_hosts.add(host)
26
+ if url_has_allowed_host_and_scheme(url=referer, allowed_hosts=allowed_hosts, require_https=request.is_secure()):
27
+ return referer
28
+ return fallback
29
+
13
30
  # Universal Downloader
14
31
  #####################################################################
15
32
  def fetch_file(request, data, file_type=None):
@@ -38,7 +55,7 @@ def fetch_file(request, data, file_type=None):
38
55
 
39
56
  if not records:
40
57
  messages.error(request, "لا توجد سجلات للتحميل.")
41
- return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
58
+ return HttpResponseRedirect(_safe_referer(request))
42
59
 
43
60
  # 2. Collect Files
44
61
  files_to_download = []
@@ -78,7 +95,7 @@ def fetch_file(request, data, file_type=None):
78
95
  # 3. Decision: Error, Single File, or Zip
79
96
  if not files_to_download:
80
97
  messages.error(request, "لا توجد ملفات صالحة للتحميل في السجلات المختارة.")
81
- return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
98
+ return HttpResponseRedirect(_safe_referer(request))
82
99
 
83
100
  if len(files_to_download) == 1:
84
101
  # Serve Single File
@@ -248,7 +265,7 @@ def fetch_excel(request, queryset, exclude_fields=None, hidden_fields=None, shee
248
265
  """
249
266
  if not queryset:
250
267
  messages.error(request, "لا توجد بيانات للتصدير.")
251
- return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
268
+ return HttpResponseRedirect(_safe_referer(request))
252
269
 
253
270
  # Normalize to iterable if list passed
254
271
  data_list = queryset
@@ -260,7 +277,7 @@ def fetch_excel(request, queryset, exclude_fields=None, hidden_fields=None, shee
260
277
 
261
278
  if not model:
262
279
  messages.error(request, "تعذر تحديد نموذج البيانات.")
263
- return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
280
+ return HttpResponseRedirect(_safe_referer(request))
264
281
 
265
282
  from openpyxl.utils import get_column_letter
266
283
  from django.db.models import FileField, DateTimeField
@@ -0,0 +1,72 @@
1
+ from functools import lru_cache
2
+ from django.conf import settings
3
+ from django.templatetags.static import static
4
+
5
+ # Registry for built-in fonts provided by the package
6
+ _FONT_REGISTRY = (
7
+ {
8
+ 'slug': 'shabwa',
9
+ 'family': 'Shabwa',
10
+ 'label': 'Shabwa',
11
+ 'variants': [
12
+ {'weight': 400, 'path': 'microsys/fonts/shabwa-regular.woff2'},
13
+ {'weight': 600, 'path': 'microsys/fonts/shabwa-medium.woff2'},
14
+ {'weight': 800, 'path': 'microsys/fonts/shabwa-bold.woff2'},
15
+ ]
16
+ },
17
+ {
18
+ 'slug': 'cairo',
19
+ 'family': 'Cairo',
20
+ 'label': 'Cairo',
21
+ 'variants': [
22
+ {'weight': 400, 'path': 'microsys/fonts/cairo-regular.woff2'},
23
+ {'weight': 600, 'path': 'microsys/fonts/cairo-medium.woff2'},
24
+ {'weight': 800, 'path': 'microsys/fonts/cairo-bold.woff2'},
25
+ ]
26
+ },
27
+ )
28
+
29
+ @lru_cache(maxsize=1)
30
+ def get_builtin_fonts():
31
+ """Returns the list of hardcoded fonts available in the package."""
32
+ return _FONT_REGISTRY
33
+
34
+ def get_font_choices():
35
+ """Returns a tuple of (slug, label) for form fields."""
36
+ return tuple((f['slug'], f['label']) for f in _FONT_REGISTRY)
37
+
38
+ def get_font_by_slug(slug):
39
+ """Returns the font configuration dict for a given slug."""
40
+ for font in _FONT_REGISTRY:
41
+ if font['slug'] == slug:
42
+ return font
43
+ return None
44
+
45
+ def generate_font_face_css(allowed_fonts=None):
46
+ """
47
+ Generates the @font-face CSS block for all allowed fonts.
48
+ This can be injected into the base template.
49
+ """
50
+ fonts_to_load = []
51
+ if allowed_fonts:
52
+ for slug in allowed_fonts:
53
+ font = get_font_by_slug(slug)
54
+ if font:
55
+ fonts_to_load.append(font)
56
+ else:
57
+ fonts_to_load = _FONT_REGISTRY
58
+
59
+ css_lines = []
60
+ for font in fonts_to_load:
61
+ for variant in font['variants']:
62
+ # We use static() to resolve the path correctly
63
+ url = static(variant['path'])
64
+ css_lines.append(f"@font-face {{")
65
+ css_lines.append(f" font-family: '{font['family']}';")
66
+ css_lines.append(f" font-weight: {variant['weight']};")
67
+ css_lines.append(f" font-style: normal;")
68
+ css_lines.append(f" src: url('{url}') format('woff2');")
69
+ css_lines.append(f" font-display: swap;")
70
+ css_lines.append(f"}}")
71
+
72
+ return "\n".join(css_lines)
@@ -73,12 +73,15 @@ from .utils import (
73
73
  normalize_sidebar_behavior,
74
74
  normalize_system_names,
75
75
  normalize_titlebar_config,
76
+ normalize_allowed_fonts,
76
77
  )
77
78
  from .widgets import MicrosysChoiceSelectorWidget
78
79
 
79
80
  User = get_user_model()
80
81
 
81
82
  THEME_CHOICES = get_theme_choices()
83
+ from .fonts import get_font_choices
84
+ FONT_CHOICES = get_font_choices()
82
85
  PERMISSION_UI_EXCLUDED_APP_LABELS = [
83
86
  'admin',
84
87
  'contenttypes',
@@ -1456,6 +1459,18 @@ class SystemSettingsForm(forms.ModelForm):
1456
1459
  required=False,
1457
1460
  initial=True,
1458
1461
  )
1462
+ allowed_fonts = forms.MultipleChoiceField(
1463
+ required=False,
1464
+ choices=FONT_CHOICES,
1465
+ )
1466
+ allow_user_font_override = forms.BooleanField(
1467
+ required=False,
1468
+ initial=True,
1469
+ )
1470
+ default_fonts = forms.CharField(
1471
+ widget=forms.HiddenInput(),
1472
+ required=False,
1473
+ )
1459
1474
  default_table_density = forms.ChoiceField(
1460
1475
  required=True,
1461
1476
  choices=TABLE_DENSITY_CHOICES,
@@ -1629,6 +1644,9 @@ class SystemSettingsForm(forms.ModelForm):
1629
1644
  'default_theme',
1630
1645
  'allowed_themes',
1631
1646
  'allow_user_theme_override',
1647
+ 'allowed_fonts',
1648
+ 'allow_user_font_override',
1649
+ 'default_fonts',
1632
1650
  'allow_user_language_override',
1633
1651
  'default_table_density',
1634
1652
  'email_2fa',
@@ -1665,7 +1683,7 @@ class SystemSettingsForm(forms.ModelForm):
1665
1683
  parsed_step = int(raw_step)
1666
1684
  except (TypeError, ValueError):
1667
1685
  parsed_step = None
1668
- if parsed_step in (0, 1, 2, 3, 4):
1686
+ if parsed_step in (0, 1, 2, 3, 4, 5):
1669
1687
  self.single_step_mode = True
1670
1688
  self.single_step_index = parsed_step
1671
1689
 
@@ -1753,11 +1771,27 @@ class SystemSettingsForm(forms.ModelForm):
1753
1771
  'help_sys_allow_user_language_override',
1754
1772
  'Allow users to change their display language from Options. When disabled, the system default language is enforced.',
1755
1773
  )
1774
+ self.fields['allowed_fonts'].label = s.get('form_sys_allowed_fonts', 'Allowed fonts')
1775
+ self.fields['allowed_fonts'].help_text = s.get(
1776
+ 'help_sys_allowed_fonts',
1777
+ 'Choose which fonts are available in this project. The default fonts for each language must remain enabled.',
1778
+ )
1779
+ self.fields['allow_user_font_override'].label = s.get('form_sys_allow_user_font_override', 'Allow user font override')
1780
+ self.fields['allow_user_font_override'].help_text = s.get(
1781
+ 'help_sys_allow_user_font_override',
1782
+ 'Allow users to switch between the allowed fonts at runtime from Options.',
1783
+ )
1784
+ self.fields['default_fonts'].label = s.get('form_sys_default_fonts', 'Default fonts by language')
1756
1785
  self.fields['default_table_density'].label = s.get('form_sys_default_table_density', "Default Table Density")
1757
1786
  self.fields['default_table_density'].help_text = s.get(
1758
1787
  'help_sys_default_table_density',
1759
1788
  'Choose the default table density for new users; each user can still override it later from Options.',
1760
1789
  )
1790
+ self.fields['default_table_density'].choices = (
1791
+ ('dense', s.get('table_density_dense', 'Dense')),
1792
+ (DEFAULT_TABLE_DENSITY, s.get('table_density_balanced', 'Balanced')),
1793
+ ('roomy', s.get('table_density_roomy', 'Roomy')),
1794
+ )
1761
1795
  self.fields['logo'].label = s.get('form_sys_logo', "System Logo (Logo)")
1762
1796
  self.fields['favicon'].label = s.get('form_sys_favicon', "Site Icon (Favicon)")
1763
1797
  self.fields['logo'].widget = _build_archive_file_widget(
@@ -2136,6 +2170,23 @@ class SystemSettingsForm(forms.ModelForm):
2136
2170
  if (not getattr(self.instance, 'pk', None) and not getattr(self.instance, 'is_configured', False))
2137
2171
  else getattr(self.instance, 'allow_user_theme_override', config.get('allow_user_theme_override', True))
2138
2172
  )
2173
+ self.initial['allow_user_font_override'] = bool(
2174
+ config.get('allow_user_font_override', True)
2175
+ if (not getattr(self.instance, 'pk', None) and not getattr(self.instance, 'is_configured', False))
2176
+ else getattr(self.instance, 'allow_user_font_override', config.get('allow_user_font_override', True))
2177
+ )
2178
+ initial_allowed_fonts = normalize_allowed_fonts(
2179
+ (
2180
+ config.get('allowed_fonts')
2181
+ if (not getattr(self.instance, 'pk', None) and not getattr(self.instance, 'is_configured', False))
2182
+ else getattr(self.instance, 'allowed_fonts', None)
2183
+ ) or config.get('allowed_fonts')
2184
+ )
2185
+ self.initial['allowed_fonts'] = list(initial_allowed_fonts)
2186
+ instance_default_fonts = getattr(self.instance, 'default_fonts', {}) or {}
2187
+ if not instance_default_fonts:
2188
+ instance_default_fonts = config.get('default_fonts', {})
2189
+ self.initial['default_fonts'] = _json_dump(instance_default_fonts, ensure_ascii=False)
2139
2190
  self.initial['allow_user_language_override'] = bool(
2140
2191
  config.get('allow_user_language_override', True)
2141
2192
  if (not getattr(self.instance, 'pk', None) and not getattr(self.instance, 'is_configured', False))
@@ -2267,12 +2318,12 @@ class SystemSettingsForm(forms.ModelForm):
2267
2318
  sidebar_config['home_url_name'] = None
2268
2319
  self.initial['sidebar_config'] = _json_dump(sidebar_config, ensure_ascii=False)
2269
2320
 
2270
- try:
2271
- initial_sidebar_config = json.loads(self.initial.get('sidebar_config') or '{}')
2272
- except (TypeError, ValueError):
2273
- initial_sidebar_config = {}
2274
- if not isinstance(initial_sidebar_config, dict):
2275
- initial_sidebar_config = {}
2321
+ initial_sidebar_config = self.initial.get('sidebar_config') or {}
2322
+ if isinstance(initial_sidebar_config, str):
2323
+ try:
2324
+ initial_sidebar_config = json.loads(initial_sidebar_config)
2325
+ except (TypeError, ValueError, json.JSONDecodeError):
2326
+ initial_sidebar_config = {}
2276
2327
 
2277
2328
  self.initial['sidebar_enabled'] = bool(initial_sidebar_config.get('enabled', True))
2278
2329
  self.initial['sidebar_enable_reorder'] = bool(initial_sidebar_config.get('enable_reorder', True))
@@ -2329,26 +2380,32 @@ class SystemSettingsForm(forms.ModelForm):
2329
2380
  self.fields['public_root_url_discovered'].widget.option_meta = home_url_option_meta
2330
2381
  self.initial['public_root_url_discovered'] = current_public_root_url if current_public_root_url in seen_home_urls else ''
2331
2382
 
2332
- try:
2333
- initial_languages = json.loads(self.initial.get('languages') or '{}')
2334
- except (TypeError, ValueError):
2335
- initial_languages = {}
2383
+ initial_languages = self.initial.get('languages') or {}
2384
+ if isinstance(initial_languages, str):
2385
+ try:
2386
+ initial_languages = json.loads(initial_languages)
2387
+ except (TypeError, ValueError, json.JSONDecodeError):
2388
+ initial_languages = {}
2336
2389
  current_languages = normalize_language_catalog(initial_languages)
2337
2390
  self.initial['languages'] = _json_dump(current_languages, ensure_ascii=False)
2338
2391
  if self.initial.get('default_language') not in current_languages:
2339
2392
  self.initial['default_language'] = 'en' if 'en' in current_languages else next(iter(current_languages), 'en')
2340
2393
 
2341
- try:
2342
- initial_system_names = json.loads(self.initial.get('system_names') or '{}')
2343
- except (TypeError, ValueError):
2344
- initial_system_names = {}
2394
+ initial_system_names = self.initial.get('system_names') or {}
2395
+ if isinstance(initial_system_names, str):
2396
+ try:
2397
+ initial_system_names = json.loads(initial_system_names)
2398
+ except (TypeError, ValueError, json.JSONDecodeError):
2399
+ initial_system_names = {}
2345
2400
  initial_system_names = normalize_system_names(initial_system_names)
2346
2401
  self.initial['system_names'] = _json_dump(initial_system_names, ensure_ascii=False)
2347
2402
 
2348
- try:
2349
- initial_translation_overrides = json.loads(self.initial.get('translations_override') or '{}')
2350
- except (TypeError, ValueError):
2351
- initial_translation_overrides = {}
2403
+ initial_translation_overrides = self.initial.get('translations_override') or {}
2404
+ if isinstance(initial_translation_overrides, str):
2405
+ try:
2406
+ initial_translation_overrides = json.loads(initial_translation_overrides)
2407
+ except (TypeError, ValueError, json.JSONDecodeError):
2408
+ initial_translation_overrides = {}
2352
2409
  if not isinstance(initial_translation_overrides, dict):
2353
2410
  initial_translation_overrides = {}
2354
2411
  suggested_languages = [
@@ -2410,13 +2467,45 @@ class SystemSettingsForm(forms.ModelForm):
2410
2467
  'picker_mode': 'setup',
2411
2468
  'input_id': 'id_default_theme',
2412
2469
  'allowed_input_name': 'allowed_themes',
2413
- 'allowed_themes': set(self.initial.get('allowed_themes', [])),
2470
+ 'allowed_themes': set(self.initial.get('allowed_themes') if isinstance(self.initial.get('allowed_themes'), (list, tuple, set)) else []),
2414
2471
  'MS_TRANS': s,
2415
2472
  'MICROSYS_THEMES': get_theme_options(s),
2416
2473
  'label': self.fields['default_theme'].label,
2417
2474
  'help_text': self.fields['allowed_themes'].help_text,
2418
2475
  },
2419
2476
  )
2477
+
2478
+ from .fonts import get_builtin_fonts
2479
+ self.font_picker_html = render_to_string(
2480
+ 'microsys/includes/font_settings_matrix.html',
2481
+ {
2482
+ 'picker_mode': 'setup',
2483
+ 'input_id': 'id_allowed_fonts',
2484
+ 'allowed_input_name': 'allowed_fonts',
2485
+ 'allowed_fonts': set(self.initial.get('allowed_fonts') if isinstance(self.initial.get('allowed_fonts'), (list, tuple, set)) else []),
2486
+ 'MS_TRANS': s,
2487
+ 'MICROSYS_FONTS': get_builtin_fonts(),
2488
+ 'label': self.fields['allowed_fonts'].label,
2489
+ 'help_text': self.fields['allowed_fonts'].help_text,
2490
+ },
2491
+ )
2492
+
2493
+ default_fonts_data = self.initial.get('default_fonts') or {}
2494
+ if isinstance(default_fonts_data, str):
2495
+ try:
2496
+ default_fonts_data = json.loads(default_fonts_data)
2497
+ except (TypeError, ValueError, json.JSONDecodeError):
2498
+ default_fonts_data = {}
2499
+
2500
+ self.language_fonts_editor_html = render_to_string(
2501
+ 'microsys/includes/language_fonts_editor.html',
2502
+ {
2503
+ 'current_languages': current_languages,
2504
+ 'default_fonts': default_fonts_data,
2505
+ 'MICROSYS_FONTS': get_builtin_fonts(),
2506
+ 'MS_TRANS': s,
2507
+ },
2508
+ )
2420
2509
 
2421
2510
  self.sidebar_builder_html = render_to_string(
2422
2511
  'microsys/includes/sidebar_builder.html',
@@ -2424,7 +2513,7 @@ class SystemSettingsForm(forms.ModelForm):
2424
2513
  'sidebar_catalog': self.sidebar_catalog,
2425
2514
  'sidebar_catalog_json': _json_dump(self.sidebar_catalog, ensure_ascii=False),
2426
2515
  'sidebar_catalog_fallback_json': _json_dump(self.sidebar_catalog_fallback, ensure_ascii=False),
2427
- 'sidebar_config_json': self.initial.get('sidebar_config', '{}'),
2516
+ 'sidebar_config_json': _json_dump(self.initial.get('sidebar_config', {}), ensure_ascii=False),
2428
2517
  'mode': self.mode,
2429
2518
  'MS_TRANS': s,
2430
2519
  },
@@ -2664,21 +2753,7 @@ class SystemSettingsForm(forms.ModelForm):
2664
2753
  css_class=_step_css_class(3),
2665
2754
  ),
2666
2755
  Div(
2667
- HTML(f"<div class='mb-3'><span class='badge rounded-pill text-bg-primary'>{s.get('system_setup_step5', 'Step 5: Appearance')}</span></div>"),
2668
- Row(
2669
- Div(
2670
- HTML(self.theme_picker_html),
2671
- Field('default_theme'),
2672
- css_class='mb-3'
2673
- ),
2674
- ),
2675
- Row(
2676
- build_settings_toggle_field(self, 'allow_user_theme_override', css_class='col-12')
2677
- ),
2678
- HTML(f"<h6 class='fw-bold my-3'>{s.get('tables_settings_title', 'Tables Settings')}</h6>"),
2679
- Row(
2680
- Div(Field('default_table_density'), css_class='col'),
2681
- ),
2756
+ HTML(f"<div class='mb-3'><span class='badge rounded-pill text-bg-primary'>{s.get('system_setup_step5', 'Step 5: Titlebar')}</span></div>"),
2682
2757
  HTML(f"<h6 class='fw-bold my-3'>{s.get('titlebar_settings_title', 'Titlebar Settings')}</h6>"),
2683
2758
  Row(
2684
2759
  build_settings_toggle_field(self, 'titlebar_show_title', css_class='col-lg-6 col-xl-3'),
@@ -2700,6 +2775,31 @@ class SystemSettingsForm(forms.ModelForm):
2700
2775
  ),
2701
2776
  css_class=_step_css_class(4),
2702
2777
  ),
2778
+ Div(
2779
+ HTML(f"<div class='mb-3'><span class='badge rounded-pill text-bg-primary'>{s.get('system_setup_step6', 'Step 6: Appearance')}</span></div>"),
2780
+ Row(
2781
+ Div(
2782
+ HTML(self.theme_picker_html),
2783
+ Field('default_theme'),
2784
+ css_class='mb-3'
2785
+ ),
2786
+ ),
2787
+ Row(
2788
+ build_settings_toggle_field(self, 'allow_user_theme_override', css_class='col-12')
2789
+ ),
2790
+ HTML(f"<h6 class='fw-bold my-3'>{s.get('typography_settings_title', 'Typography Settings')}</h6>"),
2791
+ HTML(self.font_picker_html),
2792
+ # Field('allowed_fonts'),
2793
+ build_settings_toggle_field(self, 'allow_user_font_override', css_class='col-12 mt-2'),
2794
+ HTML(self.language_fonts_editor_html),
2795
+ Field('default_fonts'),
2796
+ HTML(f"<h6 class='fw-bold my-3'>{s.get('tables_settings_title', 'Tables Settings')}</h6>"),
2797
+ Row(
2798
+ Div(Field('default_table_density'), css_class='col'),
2799
+ css_class='mb-3'
2800
+ ),
2801
+ css_class=_step_css_class(5),
2802
+ ),
2703
2803
  FormActions(
2704
2804
  HTML(
2705
2805
  f"<div class='d-flex flex-wrap justify-content-end align-items-center gap-2 mt-4 ms-setup-wizard-actions' dir='{_get_ui_direction()}'>"
@@ -2772,6 +2872,24 @@ class SystemSettingsForm(forms.ModelForm):
2772
2872
  def clean_default_language(self):
2773
2873
  return str(self.cleaned_data.get('default_language') or 'en').strip().lower().replace('_', '-')
2774
2874
 
2875
+ def clean_allowed_fonts(self):
2876
+ data = self.cleaned_data.get('allowed_fonts')
2877
+ if not data:
2878
+ return []
2879
+ return list(data)
2880
+
2881
+ def clean_default_fonts(self):
2882
+ data = self.cleaned_data.get('default_fonts')
2883
+ if not data:
2884
+ return {}
2885
+ try:
2886
+ parsed = json.loads(data) if isinstance(data, str) else data
2887
+ if not isinstance(parsed, dict):
2888
+ return {}
2889
+ return parsed
2890
+ except json.JSONDecodeError:
2891
+ return {}
2892
+
2775
2893
  def clean_default_theme(self):
2776
2894
  value = self.cleaned_data.get('default_theme') or 'light'
2777
2895
  if not is_valid_theme(value):
@@ -3152,6 +3270,9 @@ class SystemSettingsForm(forms.ModelForm):
3152
3270
  instance.translations_override = self.cleaned_data.get('translations_override', {})
3153
3271
  instance.allowed_themes = self.cleaned_data.get('allowed_themes', list(normalize_allowed_themes()))
3154
3272
  instance.allow_user_theme_override = bool(self.cleaned_data.get('allow_user_theme_override', True))
3273
+ instance.allowed_fonts = self.cleaned_data.get('allowed_fonts', [])
3274
+ instance.allow_user_font_override = bool(self.cleaned_data.get('allow_user_font_override', True))
3275
+ instance.default_fonts = self.cleaned_data.get('default_fonts', {})
3155
3276
  instance.allow_user_language_override = bool(self.cleaned_data.get('allow_user_language_override', True))
3156
3277
  instance.client_ip_config = self.cleaned_data.get('client_ip_config', default_client_ip_config())
3157
3278
  instance.titlebar_config = self.cleaned_data.get('titlebar_config', default_titlebar_config())
@@ -0,0 +1,29 @@
1
+ # Generated by Django 6.0.5 on 2026-05-16 09:39
2
+
3
+ import microsys.models
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('microsys', '0004_client_ip_and_trusted_devices'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='systemsettings',
16
+ name='allow_user_font_override',
17
+ field=models.BooleanField(default=True, verbose_name='Allow User Font Override'),
18
+ ),
19
+ migrations.AddField(
20
+ model_name='systemsettings',
21
+ name='allowed_fonts',
22
+ field=models.JSONField(blank=True, default=microsys.models.default_allowed_fonts, verbose_name='Allowed Fonts'),
23
+ ),
24
+ migrations.AddField(
25
+ model_name='systemsettings',
26
+ name='default_fonts',
27
+ field=models.JSONField(blank=True, default=dict, verbose_name='Default Fonts by Language'),
28
+ ),
29
+ ]