django-microsys 2.1.9__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. {django_microsys-2.1.9 → django_microsys-2.2.0}/PKG-INFO +1 -1
  2. {django_microsys-2.1.9 → django_microsys-2.2.0}/django_microsys.egg-info/PKG-INFO +1 -1
  3. {django_microsys-2.1.9 → django_microsys-2.2.0}/django_microsys.egg-info/SOURCES.txt +12 -4
  4. django_microsys-2.2.0/microsys/VERSION +1 -0
  5. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/context_processors.py +29 -2
  6. django_microsys-2.2.0/microsys/fonts.py +72 -0
  7. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/forms.py +147 -33
  8. django_microsys-2.2.0/microsys/migrations/0005_systemsettings_allow_user_font_override_and_more.py +29 -0
  9. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/models.py +17 -17
  10. django_microsys-2.2.0/microsys/static/microsys/fonts/cairo-bold.woff2 +0 -0
  11. django_microsys-2.2.0/microsys/static/microsys/fonts/cairo-medium.woff2 +0 -0
  12. django_microsys-2.2.0/microsys/static/microsys/fonts/cairo-regular.woff2 +0 -0
  13. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/language/css/main.css +1 -1
  14. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/main.css +17 -25
  15. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/js/base_head.js +7 -0
  16. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/js/base_runtime.js +1 -2
  17. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/js/options.js +21 -0
  18. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/js/system_setup.js +64 -1
  19. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/base.html +9 -3
  20. django_microsys-2.2.0/microsys/templates/microsys/includes/font_previews.html +23 -0
  21. django_microsys-2.2.0/microsys/templates/microsys/includes/font_settings_matrix.html +63 -0
  22. django_microsys-2.2.0/microsys/templates/microsys/includes/language_fonts_editor.html +36 -0
  23. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/options.html +25 -1
  24. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templatetags/microsys_tags.py +11 -0
  25. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/translations.py +9 -5
  26. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/utils.py +78 -8
  27. django_microsys-2.1.9/microsys/VERSION +0 -1
  28. {django_microsys-2.1.9 → django_microsys-2.2.0}/LICENSE +0 -0
  29. {django_microsys-2.1.9 → django_microsys-2.2.0}/MANIFEST.in +0 -0
  30. {django_microsys-2.1.9 → django_microsys-2.2.0}/README.md +0 -0
  31. {django_microsys-2.1.9 → django_microsys-2.2.0}/django_microsys.egg-info/dependency_links.txt +0 -0
  32. {django_microsys-2.1.9 → django_microsys-2.2.0}/django_microsys.egg-info/entry_points.txt +0 -0
  33. {django_microsys-2.1.9 → django_microsys-2.2.0}/django_microsys.egg-info/requires.txt +0 -0
  34. {django_microsys-2.1.9 → django_microsys-2.2.0}/django_microsys.egg-info/top_level.txt +0 -0
  35. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/__init__.py +0 -0
  36. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/__main__.py +0 -0
  37. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/admin.py +0 -0
  38. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/api.py +0 -0
  39. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/apps.py +0 -0
  40. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/cli.py +0 -0
  41. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/constants.py +0 -0
  42. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/discovery.py +0 -0
  43. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/fetcher.py +0 -0
  44. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/filters.py +0 -0
  45. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/formats/__init__.py +0 -0
  46. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/formats/ar/__init__.py +0 -0
  47. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/formats/ar/formats.py +0 -0
  48. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/formats/en/__init__.py +0 -0
  49. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/formats/en/formats.py +0 -0
  50. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/guards.py +0 -0
  51. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/management/commands/__init__.py +0 -0
  52. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/management/commands/microsys_check.py +0 -0
  53. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/management/commands/microsys_setup.py +0 -0
  54. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/management/commands/migrator.py +0 -0
  55. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/management/commands/seed_activity_log.py +0 -0
  56. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/managers.py +0 -0
  57. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/middleware.py +0 -0
  58. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/migrations/0001_initial.py +0 -0
  59. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/migrations/0002_public_registration.py +0 -0
  60. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/migrations/0003_public_root_split.py +0 -0
  61. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/migrations/0004_client_ip_and_trusted_devices.py +0 -0
  62. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/migrations/__init__.py +0 -0
  63. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/patches.py +0 -0
  64. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/registration.py +0 -0
  65. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold.py +0 -0
  66. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/README.md.tmpl +0 -0
  67. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/__init__.py.tmpl +0 -0
  68. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/apps.py.tmpl +0 -0
  69. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/filters.py.tmpl +0 -0
  70. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/forms.py.tmpl +0 -0
  71. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/migrations/__init__.py.tmpl +0 -0
  72. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/models.py.tmpl +0 -0
  73. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/tables.py.tmpl +0 -0
  74. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/templates/example_record_confirm_delete.html.tmpl +0 -0
  75. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/templates/example_record_detail.html.tmpl +0 -0
  76. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/templates/example_record_form.html.tmpl +0 -0
  77. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/templates/example_record_list.html.tmpl +0 -0
  78. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/tests/__init__.py.tmpl +0 -0
  79. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/tests/test_app.py.tmpl +0 -0
  80. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/translations.py.tmpl +0 -0
  81. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/urls.py.tmpl +0 -0
  82. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/app/views.py.tmpl +0 -0
  83. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/.nginx/nginx.conf.tmpl +0 -0
  84. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/.secrets/.env.tmpl +0 -0
  85. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/Dockerfile.tmpl +0 -0
  86. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/README.md.tmpl +0 -0
  87. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/compose.dev.yml.tmpl +0 -0
  88. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/compose.yml.tmpl +0 -0
  89. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/dockerignore.tmpl +0 -0
  90. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/docs/README.md.tmpl +0 -0
  91. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/entrypoint.sh.tmpl +0 -0
  92. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/gitattributes.tmpl +0 -0
  93. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/gitignore.tmpl +0 -0
  94. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/gunicorn.py.tmpl +0 -0
  95. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/manage.py.tmpl +0 -0
  96. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/package/__init__.py.tmpl +0 -0
  97. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/package/asgi.py.tmpl +0 -0
  98. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/package/celery.py.tmpl +0 -0
  99. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/package/settings.py.tmpl +0 -0
  100. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/package/urls.py.tmpl +0 -0
  101. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/package/wsgi.py.tmpl +0 -0
  102. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/req.txt.tmpl +0 -0
  103. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/start.ps1.tmpl +0 -0
  104. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/start.sh.tmpl +0 -0
  105. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/tests/__init__.py.tmpl +0 -0
  106. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/tests/test_scaffold.py.tmpl +0 -0
  107. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/scaffold_templates/project/tools/smtp_relay.py.tmpl +0 -0
  108. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/signals.py +0 -0
  109. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap-icons.css +0 -0
  110. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap-icons.woff +0 -0
  111. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap-icons.woff2 +0 -0
  112. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap.bundle.min.js +0 -0
  113. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap.bundle.min.js.map +0 -0
  114. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap.min.css +0 -0
  115. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap.min.css.map +0 -0
  116. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap.rtl.min.css +0 -0
  117. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/bootstrap/bootstrap.rtl.min.css.map +0 -0
  118. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/favicon.ico +0 -0
  119. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/img/base_logo.webp +0 -0
  120. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/img/default_profile.webp +0 -0
  121. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/img/login_logo.webp +0 -0
  122. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/accessibility/css/main.css +0 -0
  123. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/accessibility/js/main.js +0 -0
  124. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/activitylog/js/main.js +0 -0
  125. {django_microsys-2.1.9/microsys/static/microsys/language → django_microsys-2.2.0/microsys/static/microsys}/fonts/TwemojiCountryFlags.woff2 +0 -0
  126. /django_microsys-2.1.9/microsys/static/microsys/main/fonts/Shabwa-Bold.woff2 → /django_microsys-2.2.0/microsys/static/microsys/fonts/shabwa-bold.woff2 +0 -0
  127. /django_microsys-2.1.9/microsys/static/microsys/main/fonts/Shabwa-Medium.woff2 → /django_microsys-2.2.0/microsys/static/microsys/fonts/shabwa-medium.woff2 +0 -0
  128. /django_microsys-2.1.9/microsys/static/microsys/main/fonts/Shabwa.woff2 → /django_microsys-2.2.0/microsys/static/microsys/fonts/shabwa-regular.woff2 +0 -0
  129. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/forms/css/file_field.css +0 -0
  130. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/forms/css/form_actions.css +0 -0
  131. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/forms/css/form_fields.css +0 -0
  132. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/forms/js/file_field.js +0 -0
  133. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/forms/js/filter_form.js +0 -0
  134. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/autofill/js/main.js +0 -0
  135. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/context_menu/css/main.css +0 -0
  136. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/context_menu/js/main.js +0 -0
  137. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/context_menu/js/section_manager.js +0 -0
  138. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/dynamic_modal/js/main.js +0 -0
  139. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/prevent_double_submit.js +0 -0
  140. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/scan_link/js/main.js +0 -0
  141. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/scan_link/js/scan_button.js +0 -0
  142. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/helpers/wizard/js/main.js +0 -0
  143. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/language/js/main.js +0 -0
  144. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/language/js/translations.js +0 -0
  145. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/buttons.css +0 -0
  146. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/dropdowns.css +0 -0
  147. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/index_cards.css +0 -0
  148. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/options.css +0 -0
  149. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/pagination.css +0 -0
  150. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/selectors.css +0 -0
  151. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/system_setup.css +0 -0
  152. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/tables.css +0 -0
  153. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/template_cleanup.css +0 -0
  154. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/css/titlebar.css +0 -0
  155. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/js/selectors.js +0 -0
  156. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/main/js/tables.js +0 -0
  157. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/sections/js/manage_sections.js +0 -0
  158. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/sidebar/css/main.css +0 -0
  159. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/sidebar/css/reorder.css +0 -0
  160. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/sidebar/css/theme_picker.css +0 -0
  161. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/sidebar/js/main.js +0 -0
  162. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/sidebar/js/preload.js +0 -0
  163. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/sidebar/js/reorder.js +0 -0
  164. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/sidebar/js/theme_picker.js +0 -0
  165. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/blue.css +0 -0
  166. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/dark.css +0 -0
  167. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/gold.css +0 -0
  168. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/gothic.css +0 -0
  169. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/green.css +0 -0
  170. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/light.css +0 -0
  171. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/mono.css +0 -0
  172. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/neon.css +0 -0
  173. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/red.css +0 -0
  174. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/css/retro.css +0 -0
  175. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/themes/js/main.js +0 -0
  176. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/tutorial/css/main.css +0 -0
  177. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/tutorial/js/driver.js +0 -0
  178. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/tutorial/js/main.js +0 -0
  179. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/css/login.css +0 -0
  180. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/css/permissions.css +0 -0
  181. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/css/profile.css +0 -0
  182. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/css/user_hub.css +0 -0
  183. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/js/login.js +0 -0
  184. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/js/manage_users.js +0 -0
  185. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/js/permissions.js +0 -0
  186. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/js/profile_2fa.js +0 -0
  187. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/js/profile_image_widget.js +0 -0
  188. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/js/twofa_verify.js +0 -0
  189. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/microsys/users/js/user_hub.js +0 -0
  190. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/vanillajs-datepicker/datepicker-bs5.min.css +0 -0
  191. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/static/vanillajs-datepicker/datepicker.min.js +0 -0
  192. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/tables.py +0 -0
  193. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/bootstrap5/layout/field_file.html +0 -0
  194. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/2fa/verify.html +0 -0
  195. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/activitylog/activity_log.html +0 -0
  196. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/activitylog/activity_log_detail_modal.html +0 -0
  197. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/form_base.html +0 -0
  198. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/forms/assets_head.html +0 -0
  199. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/forms/assets_scripts.html +0 -0
  200. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/forms/crispy_file_field.html +0 -0
  201. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/forms/file_input.html +0 -0
  202. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/forms/filter_assets_head.html +0 -0
  203. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/forms/filter_assets_scripts.html +0 -0
  204. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/helpers/dynamic_modal.html +0 -0
  205. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/helpers/dynamic_modal_combined.html +0 -0
  206. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/helpers/dynamic_modal_detail.html +0 -0
  207. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/helpers/dynamic_modal_form.html +0 -0
  208. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/helpers/dynamic_modal_list.html +0 -0
  209. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/helpers/micro_context_menu.html +0 -0
  210. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/language_catalog_editor.html +0 -0
  211. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/language_previews.html +0 -0
  212. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/sidebar_builder.html +0 -0
  213. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/sidebar_density_previews.html +0 -0
  214. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/sidebar_items.html +0 -0
  215. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/system_names_editor.html +0 -0
  216. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/system_setup.html +0 -0
  217. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/table_density_previews.html +0 -0
  218. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/theme_previews.html +0 -0
  219. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/theme_settings_matrix.html +0 -0
  220. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/titlebar.html +0 -0
  221. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/translation_matrix_editor.html +0 -0
  222. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/includes/tutorial.html +0 -0
  223. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/list_base.html +0 -0
  224. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/registration/pending.html +0 -0
  225. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/scopes/scope_actions.html +0 -0
  226. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/scopes/scope_form.html +0 -0
  227. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/scopes/scope_manager.html +0 -0
  228. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/sections/manage_sections.html +0 -0
  229. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/sections/subsection_select.html +0 -0
  230. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/sidebar/auto.html +0 -0
  231. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/sidebar/extra_groups.html +0 -0
  232. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/sidebar/main.html +0 -0
  233. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/sidebar/tree.html +0 -0
  234. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/tables/table.html +0 -0
  235. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/users/grouped_permissions.html +0 -0
  236. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/users/manage_users.html +0 -0
  237. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/users/profile.html +0 -0
  238. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/users/profile_image_widget.html +0 -0
  239. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/users/user_detail_modal.html +0 -0
  240. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/microsys/users/user_hub.html +0 -0
  241. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/registration/email/verify_registration.txt +0 -0
  242. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/registration/login.html +0 -0
  243. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/registration/register.html +0 -0
  244. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/registration/register_sent.html +0 -0
  245. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templates/registration/register_verify.html +0 -0
  246. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templatetags/__init__.py +0 -0
  247. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templatetags/microsys_translation.py +0 -0
  248. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/templatetags/sidebar_tags.py +0 -0
  249. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/themes.py +0 -0
  250. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/urls.py +0 -0
  251. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/__init__.py +0 -0
  252. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/activitylog.py +0 -0
  253. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/general.py +0 -0
  254. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/profile.py +0 -0
  255. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/registration.py +0 -0
  256. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/scopes.py +0 -0
  257. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/sections.py +0 -0
  258. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/sidebar.py +0 -0
  259. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/twofa.py +0 -0
  260. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/views/users.py +0 -0
  261. {django_microsys-2.1.9 → django_microsys-2.2.0}/microsys/widgets.py +0 -0
  262. {django_microsys-2.1.9 → django_microsys-2.2.0}/pyproject.toml +0 -0
  263. {django_microsys-2.1.9 → django_microsys-2.2.0}/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.0
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.0
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.0
@@ -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():
@@ -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
 
@@ -1748,11 +1766,21 @@ class SystemSettingsForm(forms.ModelForm):
1748
1766
  'help_sys_allow_user_theme_override',
1749
1767
  'Allow users to switch between the allowed themes at runtime from Options and the sidebar toolbar.',
1750
1768
  )
1751
- self.fields['allow_user_language_override'].label = s.get('form_sys_allow_user_language_override', 'Allow user language override')
1752
1769
  self.fields['allow_user_language_override'].help_text = s.get(
1753
1770
  'help_sys_allow_user_language_override',
1754
1771
  'Allow users to change their display language from Options. When disabled, the system default language is enforced.',
1755
1772
  )
1773
+ self.fields['allowed_fonts'].label = s.get('form_sys_allowed_fonts', 'Allowed fonts')
1774
+ self.fields['allowed_fonts'].help_text = s.get(
1775
+ 'help_sys_allowed_fonts',
1776
+ 'Choose which fonts are available in this project. The default fonts for each language must remain enabled.',
1777
+ )
1778
+ self.fields['allow_user_font_override'].label = s.get('form_sys_allow_user_font_override', 'Allow user font override')
1779
+ self.fields['allow_user_font_override'].help_text = s.get(
1780
+ 'help_sys_allow_user_font_override',
1781
+ 'Allow users to switch between the allowed fonts at runtime from Options.',
1782
+ )
1783
+ self.fields['default_fonts'].label = s.get('form_sys_default_fonts', 'Default fonts by language')
1756
1784
  self.fields['default_table_density'].label = s.get('form_sys_default_table_density', "Default Table Density")
1757
1785
  self.fields['default_table_density'].help_text = s.get(
1758
1786
  'help_sys_default_table_density',
@@ -2136,6 +2164,23 @@ class SystemSettingsForm(forms.ModelForm):
2136
2164
  if (not getattr(self.instance, 'pk', None) and not getattr(self.instance, 'is_configured', False))
2137
2165
  else getattr(self.instance, 'allow_user_theme_override', config.get('allow_user_theme_override', True))
2138
2166
  )
2167
+ self.initial['allow_user_font_override'] = bool(
2168
+ config.get('allow_user_font_override', True)
2169
+ if (not getattr(self.instance, 'pk', None) and not getattr(self.instance, 'is_configured', False))
2170
+ else getattr(self.instance, 'allow_user_font_override', config.get('allow_user_font_override', True))
2171
+ )
2172
+ initial_allowed_fonts = normalize_allowed_fonts(
2173
+ (
2174
+ config.get('allowed_fonts')
2175
+ if (not getattr(self.instance, 'pk', None) and not getattr(self.instance, 'is_configured', False))
2176
+ else getattr(self.instance, 'allowed_fonts', None)
2177
+ ) or config.get('allowed_fonts')
2178
+ )
2179
+ self.initial['allowed_fonts'] = list(initial_allowed_fonts)
2180
+ instance_default_fonts = getattr(self.instance, 'default_fonts', {}) or {}
2181
+ if not instance_default_fonts:
2182
+ instance_default_fonts = config.get('default_fonts', {})
2183
+ self.initial['default_fonts'] = _json_dump(instance_default_fonts, ensure_ascii=False)
2139
2184
  self.initial['allow_user_language_override'] = bool(
2140
2185
  config.get('allow_user_language_override', True)
2141
2186
  if (not getattr(self.instance, 'pk', None) and not getattr(self.instance, 'is_configured', False))
@@ -2267,12 +2312,12 @@ class SystemSettingsForm(forms.ModelForm):
2267
2312
  sidebar_config['home_url_name'] = None
2268
2313
  self.initial['sidebar_config'] = _json_dump(sidebar_config, ensure_ascii=False)
2269
2314
 
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 = {}
2315
+ initial_sidebar_config = self.initial.get('sidebar_config') or {}
2316
+ if isinstance(initial_sidebar_config, str):
2317
+ try:
2318
+ initial_sidebar_config = json.loads(initial_sidebar_config)
2319
+ except (TypeError, ValueError, json.JSONDecodeError):
2320
+ initial_sidebar_config = {}
2276
2321
 
2277
2322
  self.initial['sidebar_enabled'] = bool(initial_sidebar_config.get('enabled', True))
2278
2323
  self.initial['sidebar_enable_reorder'] = bool(initial_sidebar_config.get('enable_reorder', True))
@@ -2329,26 +2374,32 @@ class SystemSettingsForm(forms.ModelForm):
2329
2374
  self.fields['public_root_url_discovered'].widget.option_meta = home_url_option_meta
2330
2375
  self.initial['public_root_url_discovered'] = current_public_root_url if current_public_root_url in seen_home_urls else ''
2331
2376
 
2332
- try:
2333
- initial_languages = json.loads(self.initial.get('languages') or '{}')
2334
- except (TypeError, ValueError):
2335
- initial_languages = {}
2377
+ initial_languages = self.initial.get('languages') or {}
2378
+ if isinstance(initial_languages, str):
2379
+ try:
2380
+ initial_languages = json.loads(initial_languages)
2381
+ except (TypeError, ValueError, json.JSONDecodeError):
2382
+ initial_languages = {}
2336
2383
  current_languages = normalize_language_catalog(initial_languages)
2337
2384
  self.initial['languages'] = _json_dump(current_languages, ensure_ascii=False)
2338
2385
  if self.initial.get('default_language') not in current_languages:
2339
2386
  self.initial['default_language'] = 'en' if 'en' in current_languages else next(iter(current_languages), 'en')
2340
2387
 
2341
- try:
2342
- initial_system_names = json.loads(self.initial.get('system_names') or '{}')
2343
- except (TypeError, ValueError):
2344
- initial_system_names = {}
2388
+ initial_system_names = self.initial.get('system_names') or {}
2389
+ if isinstance(initial_system_names, str):
2390
+ try:
2391
+ initial_system_names = json.loads(initial_system_names)
2392
+ except (TypeError, ValueError, json.JSONDecodeError):
2393
+ initial_system_names = {}
2345
2394
  initial_system_names = normalize_system_names(initial_system_names)
2346
2395
  self.initial['system_names'] = _json_dump(initial_system_names, ensure_ascii=False)
2347
2396
 
2348
- try:
2349
- initial_translation_overrides = json.loads(self.initial.get('translations_override') or '{}')
2350
- except (TypeError, ValueError):
2351
- initial_translation_overrides = {}
2397
+ initial_translation_overrides = self.initial.get('translations_override') or {}
2398
+ if isinstance(initial_translation_overrides, str):
2399
+ try:
2400
+ initial_translation_overrides = json.loads(initial_translation_overrides)
2401
+ except (TypeError, ValueError, json.JSONDecodeError):
2402
+ initial_translation_overrides = {}
2352
2403
  if not isinstance(initial_translation_overrides, dict):
2353
2404
  initial_translation_overrides = {}
2354
2405
  suggested_languages = [
@@ -2410,13 +2461,45 @@ class SystemSettingsForm(forms.ModelForm):
2410
2461
  'picker_mode': 'setup',
2411
2462
  'input_id': 'id_default_theme',
2412
2463
  'allowed_input_name': 'allowed_themes',
2413
- 'allowed_themes': set(self.initial.get('allowed_themes', [])),
2464
+ 'allowed_themes': set(self.initial.get('allowed_themes') if isinstance(self.initial.get('allowed_themes'), (list, tuple, set)) else []),
2414
2465
  'MS_TRANS': s,
2415
2466
  'MICROSYS_THEMES': get_theme_options(s),
2416
2467
  'label': self.fields['default_theme'].label,
2417
2468
  'help_text': self.fields['allowed_themes'].help_text,
2418
2469
  },
2419
2470
  )
2471
+
2472
+ from .fonts import get_builtin_fonts
2473
+ self.font_picker_html = render_to_string(
2474
+ 'microsys/includes/font_settings_matrix.html',
2475
+ {
2476
+ 'picker_mode': 'setup',
2477
+ 'input_id': 'id_allowed_fonts',
2478
+ 'allowed_input_name': 'allowed_fonts',
2479
+ 'allowed_fonts': set(self.initial.get('allowed_fonts') if isinstance(self.initial.get('allowed_fonts'), (list, tuple, set)) else []),
2480
+ 'MS_TRANS': s,
2481
+ 'MICROSYS_FONTS': get_builtin_fonts(),
2482
+ 'label': self.fields['allowed_fonts'].label,
2483
+ 'help_text': self.fields['allowed_fonts'].help_text,
2484
+ },
2485
+ )
2486
+
2487
+ default_fonts_data = self.initial.get('default_fonts') or {}
2488
+ if isinstance(default_fonts_data, str):
2489
+ try:
2490
+ default_fonts_data = json.loads(default_fonts_data)
2491
+ except (TypeError, ValueError, json.JSONDecodeError):
2492
+ default_fonts_data = {}
2493
+
2494
+ self.language_fonts_editor_html = render_to_string(
2495
+ 'microsys/includes/language_fonts_editor.html',
2496
+ {
2497
+ 'current_languages': current_languages,
2498
+ 'default_fonts': default_fonts_data,
2499
+ 'MICROSYS_FONTS': get_builtin_fonts(),
2500
+ 'MS_TRANS': s,
2501
+ },
2502
+ )
2420
2503
 
2421
2504
  self.sidebar_builder_html = render_to_string(
2422
2505
  'microsys/includes/sidebar_builder.html',
@@ -2424,7 +2507,7 @@ class SystemSettingsForm(forms.ModelForm):
2424
2507
  'sidebar_catalog': self.sidebar_catalog,
2425
2508
  'sidebar_catalog_json': _json_dump(self.sidebar_catalog, ensure_ascii=False),
2426
2509
  'sidebar_catalog_fallback_json': _json_dump(self.sidebar_catalog_fallback, ensure_ascii=False),
2427
- 'sidebar_config_json': self.initial.get('sidebar_config', '{}'),
2510
+ 'sidebar_config_json': _json_dump(self.initial.get('sidebar_config', {}), ensure_ascii=False),
2428
2511
  'mode': self.mode,
2429
2512
  'MS_TRANS': s,
2430
2513
  },
@@ -2664,17 +2747,7 @@ class SystemSettingsForm(forms.ModelForm):
2664
2747
  css_class=_step_css_class(3),
2665
2748
  ),
2666
2749
  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
- ),
2750
+ HTML(f"<div class='mb-3'><span class='badge rounded-pill text-bg-primary'>{s.get('system_setup_step5', 'Step 5: UI & Layout')}</span></div>"),
2678
2751
  HTML(f"<h6 class='fw-bold my-3'>{s.get('tables_settings_title', 'Tables Settings')}</h6>"),
2679
2752
  Row(
2680
2753
  Div(Field('default_table_density'), css_class='col'),
@@ -2700,6 +2773,26 @@ class SystemSettingsForm(forms.ModelForm):
2700
2773
  ),
2701
2774
  css_class=_step_css_class(4),
2702
2775
  ),
2776
+ Div(
2777
+ HTML(f"<div class='mb-3'><span class='badge rounded-pill text-bg-primary'>{s.get('system_setup_step6', 'Step 6: Appearance')}</span></div>"),
2778
+ Row(
2779
+ Div(
2780
+ HTML(self.theme_picker_html),
2781
+ Field('default_theme'),
2782
+ css_class='mb-3'
2783
+ ),
2784
+ ),
2785
+ Row(
2786
+ build_settings_toggle_field(self, 'allow_user_theme_override', css_class='col-12')
2787
+ ),
2788
+ HTML(f"<h6 class='fw-bold my-3'>{s.get('typography_settings_title', 'Typography Settings')}</h6>"),
2789
+ HTML(self.font_picker_html),
2790
+ # Field('allowed_fonts'),
2791
+ build_settings_toggle_field(self, 'allow_user_font_override', css_class='col-12 mt-2'),
2792
+ HTML(self.language_fonts_editor_html),
2793
+ Field('default_fonts'),
2794
+ css_class=_step_css_class(5),
2795
+ ),
2703
2796
  FormActions(
2704
2797
  HTML(
2705
2798
  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 +2865,24 @@ class SystemSettingsForm(forms.ModelForm):
2772
2865
  def clean_default_language(self):
2773
2866
  return str(self.cleaned_data.get('default_language') or 'en').strip().lower().replace('_', '-')
2774
2867
 
2868
+ def clean_allowed_fonts(self):
2869
+ data = self.cleaned_data.get('allowed_fonts')
2870
+ if not data:
2871
+ return []
2872
+ return list(data)
2873
+
2874
+ def clean_default_fonts(self):
2875
+ data = self.cleaned_data.get('default_fonts')
2876
+ if not data:
2877
+ return {}
2878
+ try:
2879
+ parsed = json.loads(data) if isinstance(data, str) else data
2880
+ if not isinstance(parsed, dict):
2881
+ return {}
2882
+ return parsed
2883
+ except json.JSONDecodeError:
2884
+ return {}
2885
+
2775
2886
  def clean_default_theme(self):
2776
2887
  value = self.cleaned_data.get('default_theme') or 'light'
2777
2888
  if not is_valid_theme(value):
@@ -3152,6 +3263,9 @@ class SystemSettingsForm(forms.ModelForm):
3152
3263
  instance.translations_override = self.cleaned_data.get('translations_override', {})
3153
3264
  instance.allowed_themes = self.cleaned_data.get('allowed_themes', list(normalize_allowed_themes()))
3154
3265
  instance.allow_user_theme_override = bool(self.cleaned_data.get('allow_user_theme_override', True))
3266
+ instance.allowed_fonts = self.cleaned_data.get('allowed_fonts', [])
3267
+ instance.allow_user_font_override = bool(self.cleaned_data.get('allow_user_font_override', True))
3268
+ instance.default_fonts = self.cleaned_data.get('default_fonts', {})
3155
3269
  instance.allow_user_language_override = bool(self.cleaned_data.get('allow_user_language_override', True))
3156
3270
  instance.client_ip_config = self.cleaned_data.get('client_ip_config', default_client_ip_config())
3157
3271
  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
+ ]
@@ -28,6 +28,11 @@ from datetime import timedelta
28
28
  from PIL import Image
29
29
 
30
30
 
31
+ def default_allowed_fonts():
32
+ from .fonts import get_builtin_fonts
33
+ return [f['slug'] for f in get_builtin_fonts()]
34
+
35
+
31
36
  def default_allowed_themes():
32
37
  from .themes import get_theme_names
33
38
  return list(get_theme_names())
@@ -144,6 +149,12 @@ class SingletonModel(models.Model):
144
149
  obj.registration_activation_mode = config.get('registration_activation_mode')
145
150
  if hasattr(obj, 'registration_throttle_enabled') and 'registration_throttle_enabled' in config:
146
151
  obj.registration_throttle_enabled = bool(config.get('registration_throttle_enabled'))
152
+ if hasattr(obj, 'allowed_fonts') and isinstance(config.get('allowed_fonts'), (list, tuple, set)):
153
+ obj.allowed_fonts = list(config.get('allowed_fonts'))
154
+ if hasattr(obj, 'default_fonts') and isinstance(config.get('default_fonts'), dict):
155
+ obj.default_fonts = config.get('default_fonts')
156
+ if hasattr(obj, 'allow_user_font_override') and 'allow_user_font_override' in config:
157
+ obj.allow_user_font_override = bool(config.get('allow_user_font_override'))
147
158
  obj.save()
148
159
  cache.set(cls.__name__, obj, timeout=86400)
149
160
  return obj
@@ -166,6 +177,9 @@ class SystemSettings(SingletonModel):
166
177
  )
167
178
  allowed_themes = models.JSONField(default=default_allowed_themes, blank=True, verbose_name="Allowed Themes")
168
179
  allow_user_theme_override = models.BooleanField(default=True, verbose_name="Allow User Theme Override")
180
+ allowed_fonts = models.JSONField(default=default_allowed_fonts, blank=True, verbose_name="Allowed Fonts")
181
+ default_fonts = models.JSONField(default=dict, blank=True, verbose_name="Default Fonts by Language")
182
+ allow_user_font_override = models.BooleanField(default=True, verbose_name="Allow User Font Override")
169
183
  allow_user_language_override = models.BooleanField(default=True, verbose_name="Allow User Language Override")
170
184
  home_url = models.CharField(max_length=255, default=DEFAULT_HOME_URL, verbose_name="Home URL")
171
185
  is_configured = models.BooleanField(default=False, verbose_name="Is Configured")
@@ -617,27 +631,13 @@ class UserActivityLog(ScopedModel):
617
631
  """Auto-resolve related object for dynamic modal detail view."""
618
632
  related_object = None
619
633
  if self.model_name and self.object_id:
634
+ from .utils import resolve_model_by_name
620
635
  try:
621
- target_model = None
622
- if '.' in self.model_name:
623
- try:
624
- target_model = apps.get_model(self.model_name)
625
- except LookupError:
626
- pass
627
- if not target_model:
628
- import unicodedata
629
- def normalize(s):
630
- return unicodedata.normalize('NFKD', s).casefold() if s else ""
631
- log_model_norm = normalize(self.model_name)
632
- for model in apps.get_models():
633
- if normalize(model._meta.verbose_name) == log_model_norm or \
634
- normalize(model._meta.object_name) == log_model_norm:
635
- target_model = model
636
- break
636
+ target_model = resolve_model_by_name(self.model_name)
637
637
  if target_model:
638
638
  try:
639
639
  related_object = target_model._default_manager.get(pk=self.object_id)
640
- except target_model.DoesNotExist:
640
+ except (target_model.DoesNotExist, ValueError, TypeError):
641
641
  pass
642
642
  except Exception:
643
643
  pass
@@ -73,7 +73,7 @@
73
73
  @font-face {
74
74
  font-family: 'Twemoji Country Flags';
75
75
  unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065-E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;
76
- src: url('../fonts/TwemojiCountryFlags.woff2') format('woff2');
76
+ src: url('../../fonts/TwemojiCountryFlags.woff2') format('woff2');
77
77
  font-display: swap;
78
78
  }
79
79