simpledomcontrol 0.158.8__tar.gz → 0.159.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 (209) hide show
  1. simpledomcontrol-0.159.0/LICENSE +21 -0
  2. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/PKG-INFO +13 -11
  3. simpledomcontrol-0.159.0/pyproject.toml +66 -0
  4. simpledomcontrol-0.159.0/sdc/__main__.py +92 -0
  5. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/apps.py +1 -1
  6. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/consumers.py +110 -49
  7. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/jwt_utils.py +3 -0
  8. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/init_add/sdc_core_manager.py +2 -1
  9. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_cc.py +0 -1
  10. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_db_tools.py +27 -24
  11. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_get_controller_infos.py +1 -1
  12. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_get_model_infos.py +1 -1
  13. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_init.py +1 -1
  14. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_make_model_js.py +3 -2
  15. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_new_model.py +1 -0
  16. simpledomcontrol-0.159.0/sdc_core/management/commands/sdc_open_api.py +437 -0
  17. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_overwrite_lib_file.py +10 -3
  18. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_shell_execute_script.py +2 -1
  19. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/utils/__init__.py +2 -2
  20. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/rest_api.py +80 -68
  21. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/sdc_extentions/forms.py +4 -2
  22. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/sdc_extentions/models.py +64 -30
  23. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/sdc_extentions/search.py +8 -7
  24. simpledomcontrol-0.159.0/sdc_core/sdc_extentions/test_utils.py +17 -0
  25. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/sdc_extentions/views.py +40 -6
  26. simpledomcontrol-0.159.0/sdc_core/signals.py +46 -0
  27. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/gulpfile.jsx +7 -0
  28. simpledomcontrol-0.159.0/sdc_core/template_files/Assets/tests/config/pre-test-setup.js +132 -0
  29. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/tests/config/test-setup.js +10 -6
  30. simpledomcontrol-0.159.0/sdc_core/template_files/Assets/webpack.config/webpack.default.config.jsx +63 -0
  31. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/asgi.py.txt +1 -1
  32. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/package.json +1 -1
  33. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/settings_extension.py.txt +46 -18
  34. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_model_form/sdc_model_form.js +8 -4
  35. {simpledomcontrol-0.158.8/sdc_tools/Assets/src → simpledomcontrol-0.159.0/sdc_tools/Assets/bin}/sdc_tools/controller/sdc_navigator/sdc_navigator.js +8 -4
  36. simpledomcontrol-0.159.0/sdc_tools/Assets/bin/sdc_tools/sdc_tools.organizer.js +1 -0
  37. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_model_form/sdc_model_form.js +8 -4
  38. {simpledomcontrol-0.158.8/sdc_tools/Assets/bin → simpledomcontrol-0.159.0/sdc_tools/Assets/src}/sdc_tools/controller/sdc_navigator/sdc_navigator.js +8 -4
  39. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/tests/sdc_tools.test.js +2 -2
  40. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/widgets/searchable_select.html +2 -1
  41. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/sdc_login/sdc_login.js +6 -5
  42. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/sdc_user/sdc_user.js +10 -7
  43. simpledomcontrol-0.159.0/sdc_user/Assets/bin/sdc_user/sdc_user.organizer.js +1 -0
  44. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_login/sdc_login.js +6 -5
  45. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_user/sdc_user.js +10 -7
  46. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/tests/sdc_user.test.js +2 -2
  47. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/forms.py +17 -0
  48. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/mails.py +37 -10
  49. simpledomcontrol-0.159.0/sdc_user/migrations/__init__.py +0 -0
  50. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/models.py +43 -4
  51. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/sdc_views.py +54 -12
  52. simpledomcontrol-0.159.0/sdc_user/templates/sdc_user/sdc/sdc_change_password.html +7 -0
  53. simpledomcontrol-0.158.8/pyproject.toml +0 -53
  54. simpledomcontrol-0.158.8/sdc_core/management/commands/sdc_open_api.py +0 -445
  55. simpledomcontrol-0.158.8/sdc_core/signals.py +0 -39
  56. simpledomcontrol-0.158.8/sdc_core/template_files/Assets/tests/config/pre-test-setup.js +0 -94
  57. simpledomcontrol-0.158.8/sdc_core/template_files/Assets/webpack.config/webpack.default.config.jsx +0 -56
  58. simpledomcontrol-0.158.8/sdc_tools/Assets/bin/sdc_tools/sdc_tools.organizer.js +0 -1
  59. simpledomcontrol-0.158.8/sdc_user/Assets/bin/sdc_user/sdc_user.organizer.js +0 -1
  60. simpledomcontrol-0.158.8/sdc_user/templates/sdc_user/sdc/sdc_change_password.html +0 -2
  61. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/README.md +0 -0
  62. {simpledomcontrol-0.158.8/sdc_core → simpledomcontrol-0.159.0/sdc}/__init__.py +0 -0
  63. {simpledomcontrol-0.158.8/sdc_core/management → simpledomcontrol-0.159.0/sdc_core}/__init__.py +0 -0
  64. {simpledomcontrol-0.158.8/sdc_core/management/commands → simpledomcontrol-0.159.0/sdc_core/management}/__init__.py +0 -0
  65. {simpledomcontrol-0.158.8/sdc_core/sdc_extentions → simpledomcontrol-0.159.0/sdc_core/management/commands}/__init__.py +0 -0
  66. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/_private.py +0 -0
  67. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/init_add/__init__.py +0 -0
  68. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/init_add/add_controller_manager.py +0 -0
  69. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/init_add/add_model_manager.py +0 -0
  70. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/init_add/options.py +0 -0
  71. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/init_add/settings_manager.py +0 -0
  72. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/init_add/utils.py +0 -0
  73. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_get_controller_url.py +0 -0
  74. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_is_installed.py +0 -0
  75. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_update_links.py +0 -0
  76. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/management/commands/sdc_update_url.py +0 -0
  77. {simpledomcontrol-0.158.8/sdc_core/templatetags → simpledomcontrol-0.159.0/sdc_core/sdc_extentions}/__init__.py +0 -0
  78. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/sdc_extentions/import_manager.py +0 -0
  79. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/sdc_extentions/response.py +0 -0
  80. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/.devcontainer/Dockerfile +0 -0
  81. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/.devcontainer/devcontainer.json +0 -0
  82. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/.devcontainer/on_create.sh +0 -0
  83. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/.devcontainer/on_startup.sh +0 -0
  84. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/babel.config.json +0 -0
  85. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/src/index.organizer.js +0 -0
  86. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/src/index.style.scss +0 -0
  87. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/static/img/favicon.png +0 -0
  88. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/tests/config/post-test-teardown.js +0 -0
  89. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/webpack.config/webpack.bundle.config.jsx +0 -0
  90. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/webpack.config/webpack.development.config.jsx +0 -0
  91. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/Assets/webpack.config/webpack.production.config.jsx +0 -0
  92. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/controller/template_controller.js.txt +0 -0
  93. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/controller/template_css.scss +0 -0
  94. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/controller/template_test.js.txt +0 -0
  95. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/controller/template_view.html +0 -0
  96. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/js_test.js.txt +0 -0
  97. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/models/detail.html +0 -0
  98. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/models/list.html +0 -0
  99. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/routing.py.txt +0 -0
  100. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/sdc_urls.py.txt +0 -0
  101. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/sdc_views.py.txt +0 -0
  102. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/templates/base.html +0 -0
  103. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/templates/elements/form.html +0 -0
  104. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/templates/elements/inline_form.html +0 -0
  105. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/templates/index.html +0 -0
  106. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/template_files/urls.py.txt +0 -0
  107. {simpledomcontrol-0.158.8/sdc_tools → simpledomcontrol-0.159.0/sdc_core/templatetags}/__init__.py +0 -0
  108. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/templatetags/addclass.py +0 -0
  109. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/templatetags/indexfilter.py +0 -0
  110. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_core/templatetags/sdc_filter.py +0 -0
  111. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_alert_messenger/sdc_alert_messenger.js +0 -0
  112. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_auto_submit/sdc_auto_submit.js +0 -0
  113. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_detail_view/sdc_detail_view.js +0 -0
  114. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_dummy/sdc_dummy.js +0 -0
  115. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_error/sdc_error.js +0 -0
  116. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_list_view/sdc_list_view.js +0 -0
  117. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_navigation_client/sdc_navigation_client.js +0 -0
  118. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_search_select/sdc_search_select.js +0 -0
  119. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_search_view/sdc_search_view.js +0 -0
  120. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/controller/sdc_update_on_change/sdc_update_on_change.js +0 -0
  121. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/bin/sdc_tools/sdc_tools.style.css +0 -0
  122. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_alert_messenger/sdc_alert_messenger.js +0 -0
  123. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_alert_messenger/sdc_alert_messenger.scss +0 -0
  124. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_auto_submit/sdc_auto_submit.js +0 -0
  125. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_detail_view/sdc_detail_view.js +0 -0
  126. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_detail_view/sdc_detail_view.scss +0 -0
  127. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_dummy/sdc_dummy.js +0 -0
  128. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_dummy/sdc_dummy.scss +0 -0
  129. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_error/sdc_error.js +0 -0
  130. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_error/sdc_error.scss +0 -0
  131. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_list_view/sdc_list_view.js +0 -0
  132. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_list_view/sdc_list_view.scss +0 -0
  133. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_model_form/sdc_model_form.scss +0 -0
  134. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_navigation_client/sdc_navigation_client.js +0 -0
  135. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_navigator/sdc_navigator.scss +0 -0
  136. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_search_select/sdc_search_select.js +0 -0
  137. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_search_select/sdc_search_select.scss +0 -0
  138. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_search_view/sdc_search_view.js +0 -0
  139. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_search_view/sdc_search_view.scss +0 -0
  140. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/controller/sdc_update_on_change/sdc_update_on_change.js +0 -0
  141. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/sdc_tools.organizer.js +0 -0
  142. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/Assets/src/sdc_tools/sdc_tools.style.scss +0 -0
  143. {simpledomcontrol-0.158.8/sdc_tools/migrations → simpledomcontrol-0.159.0/sdc_tools}/__init__.py +0 -0
  144. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/admin.py +0 -0
  145. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/apps.py +0 -0
  146. {simpledomcontrol-0.158.8/sdc_user → simpledomcontrol-0.159.0/sdc_tools/migrations}/__init__.py +0 -0
  147. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/models.py +0 -0
  148. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/sdc_urls.py +0 -0
  149. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/sdc_views.py +0 -0
  150. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/email/base.html +0 -0
  151. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/dummy_sdc.html +0 -0
  152. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_alert_messenger.html +0 -0
  153. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_detail_view.html +0 -0
  154. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_dummy.html +0 -0
  155. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_error.html +0 -0
  156. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_list_view.html +0 -0
  157. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_model_form.html +0 -0
  158. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_navigator.html +0 -0
  159. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_search_select.html +0 -0
  160. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/templates/sdc_tools/sdc/sdc_search_view.html +0 -0
  161. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/tests.py +0 -0
  162. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/views.py +0 -0
  163. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_tools/widgets.py +0 -0
  164. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/register/register.js +0 -0
  165. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/sdc_change_password/sdc_change_password.js +0 -0
  166. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/sdc_confirm_email/sdc_confirm_email.js +0 -0
  167. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/sdc_logout/sdc_logout.js +0 -0
  168. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/sdc_password_forgotten/sdc_password_forgotten.js +0 -0
  169. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/sdc_reset_password/sdc_reset_password.js +0 -0
  170. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/controller/sdc_user_nav_btn/sdc_user_nav_btn.js +0 -0
  171. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/bin/sdc_user/sdc_user.style.css +0 -0
  172. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/register/register.js +0 -0
  173. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/register/register.scss +0 -0
  174. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_change_password/sdc_change_password.js +0 -0
  175. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_change_password/sdc_change_password.scss +0 -0
  176. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_confirm_email/sdc_confirm_email.js +0 -0
  177. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_confirm_email/sdc_confirm_email.scss +0 -0
  178. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_login/sdc_login.scss +0 -0
  179. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_logout/sdc_logout.js +0 -0
  180. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_logout/sdc_logout.scss +0 -0
  181. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_password_forgotten/sdc_password_forgotten.js +0 -0
  182. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_password_forgotten/sdc_password_forgotten.scss +0 -0
  183. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_reset_password/sdc_reset_password.js +0 -0
  184. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_reset_password/sdc_reset_password.scss +0 -0
  185. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_user/sdc_user.scss +0 -0
  186. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_user_nav_btn/sdc_user_nav_btn.js +0 -0
  187. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/controller/sdc_user_nav_btn/sdc_user_nav_btn.scss +0 -0
  188. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/sdc_user.organizer.js +0 -0
  189. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/Assets/src/sdc_user/sdc_user.style.scss +0 -0
  190. {simpledomcontrol-0.158.8/sdc_user/migrations → simpledomcontrol-0.159.0/sdc_user}/__init__.py +0 -0
  191. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/admin.py +0 -0
  192. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/apps.py +0 -0
  193. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/migrations/0001_initial.py +0 -0
  194. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/sdc_urls.py +0 -0
  195. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/email/confirm.html +0 -0
  196. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/email/reset_password.html +0 -0
  197. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/models/SdcUser/SdcUser_details.html +0 -0
  198. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/models/SdcUser/SdcUser_list.html +0 -0
  199. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/sdc/register.html +0 -0
  200. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/sdc/sdc_confirm_email.html +0 -0
  201. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/sdc/sdc_login.html +0 -0
  202. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/sdc/sdc_logout.html +0 -0
  203. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/sdc/sdc_password_forgotten.html +0 -0
  204. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/sdc/sdc_reset_password.html +0 -0
  205. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/sdc_user/sdc/sdc_user_nav_btn.html +0 -0
  206. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/templates/widgets/read_only_password_hash.html +0 -0
  207. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/tests.py +0 -0
  208. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/views.py +0 -0
  209. {simpledomcontrol-0.158.8 → simpledomcontrol-0.159.0}/sdc_user/widgets.py +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2026 Martin Starman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,20 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SimpleDomControl
3
- Version: 0.158.8
3
+ Version: 0.159.0
4
4
  Summary: Simple DOM control -> a django MVC framework
5
- License: Apache-2.0
5
+ License-Expression: MIT
6
+ License-File: LICENSE
6
7
  Keywords: django,MVC
7
8
  Author: Martin Starman
8
9
  Author-email: private@martin-starman.com
9
10
  Requires-Python: >=3.13,<4.0
10
- Classifier: Environment :: Web Environment
11
- Classifier: License :: OSI Approved :: Apache Software License
12
- Classifier: Operating System :: OS Independent
13
11
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.13
15
- Classifier: Programming Language :: Python :: 3.14
16
- Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
12
+ Classifier: Framework :: Django
17
13
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
15
+ Classifier: Environment :: Web Environment
16
+ Classifier: Operating System :: OS Independent
17
+ Provides-Extra: postgres
18
18
  Requires-Dist: attrs (>=25.4.0,<26.0.0)
19
19
  Requires-Dist: channels (>=4.3.2,<5.0.0)
20
20
  Requires-Dist: channels-redis (>=4.3.0,<5.0.0)
@@ -22,12 +22,14 @@ Requires-Dist: click (>=8.1.0)
22
22
  Requires-Dist: daphne (>=4.2.1,<5.0.0)
23
23
  Requires-Dist: django (>=6.0.2,<7.0.0)
24
24
  Requires-Dist: inquirerpy (>=0.3.4,<0.4.0)
25
- Requires-Dist: poetry-core (>=2.3.1,<3.0.0)
26
- Requires-Dist: psycopg2-binary (>=2.9.11,<3.0.0)
25
+ Requires-Dist: psycopg2-binary (>=2.9.11,<3.0.0) ; extra == "postgres"
27
26
  Requires-Dist: pyjwt (>=2.11.0,<3.0.0)
28
27
  Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
29
28
  Requires-Dist: regex (>=2026.1.15,<2027.0.0)
30
- Project-URL: Homepage, https://github.com/StarmanMartin/sdc
29
+ Project-URL: Documentation, https://simpledomcontrol.readthedocs.io/en/latest/
30
+ Project-URL: Homepage, https://github.com/StarmanMartin/SimpleDomControl
31
+ Project-URL: Repository, https://github.com/StarmanMartin/SimpleDomControl
32
+ Project-URL: changelog, https://github.com/StarmanMartin/SimpleDomControl/blob/master/CHANGELOG.md
31
33
  Description-Content-Type: text/markdown
32
34
 
33
35
  # Simple Dom Control (SDC)
@@ -0,0 +1,66 @@
1
+ [project]
2
+ name = "SimpleDomControl"
3
+ version = "0.159.0"
4
+ description = "Simple DOM control -> a django MVC framework"
5
+ authors = [{ name = "Martin Starman", email = "private@martin-starman.com" }]
6
+ readme = "README.md"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ requires-python = ">=3.13,<4.0"
10
+ keywords = ["django", "MVC"]
11
+ classifiers = [
12
+ "Programming Language :: Python :: 3",
13
+ "Framework :: Django",
14
+ "Topic :: Software Development :: Libraries :: Python Modules",
15
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
16
+ "Environment :: Web Environment",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ dependencies = [
20
+ "daphne (>=4.2.1,<5.0.0)",
21
+ "channels (>=4.3.2,<5.0.0)",
22
+ "channels-redis (>=4.3.0,<5.0.0)",
23
+ "django (>=6.0.2,<7.0.0)",
24
+ "attrs (>=25.4.0,<26.0.0)",
25
+ "regex (>=2026.1.15,<2027.0.0)",
26
+ "click (>=8.1.0)",
27
+ "pyjwt (>=2.11.0,<3.0.0)",
28
+ "inquirerpy (>=0.3.4,<0.4.0)",
29
+ "pyyaml (>=6.0.3,<7.0.0)",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ # PostgreSQL driver: pip install "simpledomcontrol[postgres]"
34
+ postgres = ["psycopg2-binary (>=2.9.11,<3.0.0)"]
35
+
36
+ [project.urls]
37
+ homepage = "https://github.com/StarmanMartin/SimpleDomControl"
38
+ repository = "https://github.com/StarmanMartin/SimpleDomControl"
39
+ documentation = "https://simpledomcontrol.readthedocs.io/en/latest/"
40
+ changelog = "https://github.com/StarmanMartin/SimpleDomControl/blob/master/CHANGELOG.md"
41
+
42
+ [project.scripts]
43
+ sdc = "sdc.__main__:cli"
44
+
45
+ [tool.poetry]
46
+ packages = [{include = "sdc_core"}, {include = "sdc_user"}, {include = "sdc_tools"}, {include = "sdc"}]
47
+
48
+ [tool.poetry.group.dev.dependencies]
49
+ pytest = "^9.0.2"
50
+ pillow = "^12.1.0"
51
+ sphinx = "^9.1.0"
52
+ sphinx-toolbox = "^4.1.2"
53
+ sphinx-markdown-builder = "^0.6.9"
54
+ sphinx-rtd-theme = "^3.1.0"
55
+
56
+ [tool.pytest.ini_options]
57
+ pythonpath = [
58
+ "."
59
+ ]
60
+ testpaths = [
61
+ "pytests",
62
+ ]
63
+
64
+ [build-system]
65
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
66
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,92 @@
1
+ """
2
+ Command line tool of SDC: ``sdc new --name <project>`` creates a new Django project with SDC.
3
+
4
+ Installed as the console script ``sdc`` (also ``python -m sdc``).
5
+ """
6
+ import os
7
+ import re
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import venv
12
+ from importlib import metadata
13
+ from pathlib import Path
14
+
15
+ import click
16
+
17
+
18
+ def _default_package():
19
+ """The simpledomcontrol version of this installation, so the new project uses the same one."""
20
+ try:
21
+ return f"simpledomcontrol=={metadata.version('SimpleDomControl')}"
22
+ except metadata.PackageNotFoundError:
23
+ return "simpledomcontrol"
24
+
25
+
26
+ def _run(cmd, cwd):
27
+ click.echo(f"$ {' '.join(str(c) for c in cmd)}")
28
+ result = subprocess.run(cmd, cwd=cwd)
29
+ if result.returncode != 0:
30
+ raise click.ClickException(f"Command failed with exit code {result.returncode}: {' '.join(map(str, cmd))}")
31
+
32
+
33
+ def _venv_python(venv_dir: Path) -> Path:
34
+ if os.name == "nt":
35
+ return venv_dir / "Scripts" / "python.exe"
36
+ return venv_dir / "bin" / "python"
37
+
38
+
39
+ def _add_sdc_core(settings_path: Path):
40
+ text = settings_path.read_text()
41
+ if "'sdc_core'" in text or '"sdc_core"' in text:
42
+ return
43
+ new_text, count = re.subn(r"INSTALLED_APPS\s*=\s*\[", "INSTALLED_APPS = [\n 'sdc_core',", text, count=1)
44
+ if count != 1:
45
+ raise click.ClickException(f"INSTALLED_APPS not found in {settings_path}")
46
+ settings_path.write_text(new_text)
47
+
48
+
49
+ @click.group()
50
+ def cli():
51
+ pass
52
+
53
+
54
+ @cli.command(help="Creates a new SDC project in the subdirectory NAME of the current directory.")
55
+ @click.option("--name", prompt="Project name", help="Name of the new project (a valid Python identifier).")
56
+ @click.option("--package", default=None,
57
+ help="pip requirement used to install SDC into the project's virtualenv "
58
+ "(default: the installed version, e.g. simpledomcontrol==0.159.0).")
59
+ @click.option("--skip-npm", is_flag=True, help="Do not run 'npm install'.")
60
+ def new(name, package, skip_npm):
61
+ if not name.isidentifier():
62
+ raise click.BadParameter("The project name must be a valid Python identifier.", param_hint="--name")
63
+ target = Path.cwd() / name
64
+ if target.exists() and any(target.iterdir()):
65
+ raise click.ClickException(f"{target} already exists and is not empty.")
66
+ target.mkdir(parents=True, exist_ok=True)
67
+
68
+ # The virtualenv is created in its final location; moving a virtualenv breaks it.
69
+ venv_dir = target / "venv"
70
+ click.echo(f"Creating virtualenv in {venv_dir}")
71
+ venv.EnvBuilder(with_pip=True).create(venv_dir)
72
+ python = _venv_python(venv_dir)
73
+
74
+ _run([python, "-m", "pip", "install", "--upgrade", "pip"], target)
75
+ _run([python, "-m", "pip", "install", package or _default_package()], target)
76
+ _run([python, "-m", "django", "startproject", name, "."], target)
77
+ _add_sdc_core(target / name / "settings.py")
78
+ _run([python, "manage.py", "sdc_init", "-y"], target)
79
+
80
+ if skip_npm:
81
+ click.echo("Skipped 'npm install'.")
82
+ elif shutil.which("npm"):
83
+ _run(["npm", "install"], target)
84
+ else:
85
+ click.echo("npm was not found; run 'npm install' in the project directory to install the client.")
86
+
87
+ click.echo(f"\nDone. Next steps:\n cd {name}\n source venv/bin/activate\n"
88
+ f" python manage.py migrate\n npm run develop # in a second terminal: python manage.py runserver")
89
+
90
+
91
+ if __name__ == '__main__':
92
+ cli()
@@ -8,7 +8,7 @@ class SdcCoreConfig(AppConfig):
8
8
  def ready(self):
9
9
  import sys
10
10
  from django.conf import settings
11
- from sdc_core.signals import set_winner
11
+ import sdc_core.signals # noqa: F401 (registers the signal receivers)
12
12
 
13
13
  if settings.DEBUG and os.path.exists('./Assets'):
14
14
  env_vars = {}
@@ -6,6 +6,7 @@ import traceback
6
6
  from django.apps import apps
7
7
  from django.conf import settings
8
8
  from django.core.exceptions import PermissionDenied
9
+ from django.core.serializers.json import DjangoJSONEncoder
9
10
  from django.core.files.uploadhandler import TemporaryFileUploadHandler
10
11
 
11
12
  from django.utils.datastructures import MultiValueDict
@@ -14,11 +15,10 @@ from django.utils.translation import gettext as _f
14
15
  from django.contrib.auth import get_user_model
15
16
  from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
16
17
 
17
- from asgiref.sync import async_to_sync
18
- import importlib
18
+ from asgiref.sync import async_to_sync, sync_to_async
19
19
  import json
20
20
 
21
- from sdc_core.sdc_extentions.models import SdcModel, SDCSerializer, all_models, sanitize_filter_query
21
+ from sdc_core.sdc_extentions.models import SdcModel, SDCSerializer, all_models, sanitize_filter_query, resolve_form
22
22
  from sdc_core.sdc_extentions.response import sdc_link_factory, sdc_link_obj_factory
23
23
  from sdc_core.sdc_extentions.import_manager import import_function
24
24
  from sdc_core.sdc_extentions.search import handle_search_form
@@ -68,7 +68,7 @@ class MsgManager:
68
68
  return {'save': {'header': f'{model} saved', 'msg': '{0} was successfully saved'},
69
69
  'on_change': {'header': f'{model} was changed', 'msg': '{0} was changed'},
70
70
  'create': {'header': f'{model} created', 'msg': '{0} was successfully created'},
71
- 'delete': {'header': f'{model} was deleted', 'msg': '{0} was changed'}}
71
+ 'delete': {'header': f'{model} was deleted', 'msg': '{0} was deleted'}}
72
72
 
73
73
 
74
74
  class SDCConsumer(AsyncWebsocketConsumer):
@@ -115,7 +115,7 @@ class SDCConsumer(AsyncWebsocketConsumer):
115
115
  'is_error': True,
116
116
  'msg': event.get('msg', ''),
117
117
  'header': event.get('header', ''),
118
- }))
118
+ }, cls=DjangoJSONEncoder))
119
119
 
120
120
  @staticmethod
121
121
  def to_camel_case(snake_str):
@@ -147,19 +147,25 @@ class SDCConsumer(AsyncWebsocketConsumer):
147
147
  if asyncio.iscoroutinefunction(method):
148
148
  return_vals = await method(self, **json_data.get('args', {}))
149
149
  else:
150
- return_vals = method(self, **json_data.get('args', {}))
150
+ # Sync methods run in a worker thread, so they may use the Django ORM.
151
+ return_vals = await sync_to_async(method)(self, **json_data.get('args', {}))
151
152
 
152
153
  return_vals_generator = []
153
154
  if isinstance(return_vals, types.GeneratorType):
154
155
  return_vals_generator = return_vals
155
156
  return_vals = next(return_vals, None)
156
157
 
158
+ # {'is_error': True, 'msg': ..., 'header': ...} rejects the serverCall promise.
159
+ if isinstance(return_vals, dict) and return_vals.get('is_error'):
160
+ await self.state_error(return_vals, json_data['id'])
161
+ return
162
+
157
163
  await self.send(text_data=json.dumps({
158
164
  'id': json_data['id'],
159
165
  'type': 'sdc_recall',
160
166
  'data': return_vals,
161
167
  'is_error': False
162
- }))
168
+ }, cls=DjangoJSONEncoder))
163
169
  for x in return_vals_generator: pass
164
170
  else:
165
171
  raise ValueError("event must be sdc_call")
@@ -190,12 +196,21 @@ class SDCModelConsumer(WebsocketConsumer):
190
196
  self.model_name = None
191
197
  self.model = None
192
198
  self.queryset = {}
193
- self.model_id_list = []
199
+ # Filter of the last full load (connect, load, list_view, named_view). Live updates
200
+ # (on_create) are checked against it, independent of later form or detail requests.
201
+ self.live_query = {}
202
+ # Primary keys of the rows the client has received; on_update/on_delete are only
203
+ # forwarded for these.
204
+ self.ids = []
205
+ self.model_id = None
194
206
  self._upload_handler = {}
195
207
  self._group_names = []
196
208
 
197
209
  def connect(self):
198
210
  self.model_name = self.scope['url_route']['kwargs']['model_name']
211
+ # sdc_ws/model/<model_name>/<model_id> restricts the connection to one object.
212
+ model_id = self.scope['url_route']['kwargs'].get('model_id')
213
+ self.model_id = int(model_id) if model_id else None
199
214
  self.model = ALL_MODELS.get(self.model_name)
200
215
  if self.model is None or not hasattr(self.model, '__is_sdc_model__'):
201
216
  raise ValueError(f'{self.model_name} is not a SDC model')
@@ -204,22 +219,40 @@ class SDCModelConsumer(WebsocketConsumer):
204
219
  self.accept()
205
220
 
206
221
  def websocket_disconnect(self, close_code):
207
- for group in self._group_names:
208
- async_to_sync(self.channel_layer.group_discard(group, self.channel_name))
209
- hasattr(self.model.SdcMeta, 'on_disconnected') and self.model.SdcMeta.on_disconnected(self._load_model('disconnect'))
210
- super().websocket_disconnect(close_code)
222
+ try:
223
+ for group in self._group_names:
224
+ async_to_sync(self.channel_layer.group_discard)(group, self.channel_name)
225
+ on_disconnected = getattr(self.model.SdcMeta, 'on_disconnected', None) if self.model else None
226
+ if callable(on_disconnected):
227
+ on_disconnected(self._load_model('disconnect', self.live_query))
228
+ except Exception:
229
+ logger.exception("Error while closing the SDC model connection")
230
+ finally:
231
+ super().websocket_disconnect(close_code)
211
232
 
212
233
  def on_update(self, data):
213
234
  if data['pk'] in self.ids:
214
235
  self.send(text_data=json.dumps(data))
215
236
 
237
+ def on_delete(self, data):
238
+ if data['pk'] in self.ids:
239
+ self.ids.remove(data['pk'])
240
+ self.send(text_data=json.dumps(data))
241
+
216
242
  def on_create(self, data):
217
- instance = data['pk']
243
+ # Forward only rows the user may see and that match the filter of the last full load.
218
244
  try:
219
- self._load_model().get(pk=instance)
245
+ rows = self._load_model('load', self.live_query)
246
+ if hasattr(rows, 'filter'):
247
+ visible = rows.filter(pk=data['pk']).exists()
248
+ else:
249
+ visible = data['pk'] in self._pks(rows)
250
+ except Exception:
251
+ logger.exception("Error while checking a created %s for live updates", self.model_name)
252
+ visible = False
253
+ if visible:
254
+ self._track_ids([data['pk']])
220
255
  self.send(text_data=json.dumps(data))
221
- except:
222
- pass
223
256
 
224
257
  def state_error(self, event):
225
258
  self.send(text_data=json.dumps({
@@ -266,12 +299,7 @@ class SDCModelConsumer(WebsocketConsumer):
266
299
  elif event_type == 'model_delete':
267
300
  self._delete_element(json_data)
268
301
  elif event_type == 'model_load':
269
- self.send(text_data=json.dumps({
270
- 'type': json_data['event_type'],
271
- 'event_id': json_data['event_id'],
272
- 'args': self._prepare_loaded_data(),
273
- 'is_error': False
274
- }))
302
+ self._load(json_data)
275
303
  else:
276
304
  raise ValueError(
277
305
  f"{json_data['event']} must be 'model' and {json_data['event_type']} must be in [load, delete, upload, create, save, detail_view, 'connect', 'edit_form', 'create_form', 'list_view']")
@@ -287,7 +315,9 @@ class SDCModelConsumer(WebsocketConsumer):
287
315
  except Exception as e:
288
316
  extracted_list = traceback.extract_tb(e.__traceback__)
289
317
  e_text = [e.__str__()] + [item for item in traceback.StackSummary.from_list(extracted_list).format()]
318
+
290
319
  if settings.DEBUG:
320
+ logger.error(e_text[0])
291
321
  traceback.print_tb(e.__traceback__)
292
322
  else:
293
323
  logger.error(e_text)
@@ -317,21 +347,63 @@ class SDCModelConsumer(WebsocketConsumer):
317
347
  self.channel_name
318
348
  )
319
349
 
320
- def _load_model(self, event_type=None):
350
+ def _load_model(self, event_type=None, query=None):
351
+ """
352
+ Returns the rows for ``query`` (default: the filter of the current message):
353
+ ``get_queryset()`` of the model, filtered with the sanitised query, or the
354
+ result of the model's ``data_load()`` hook if it returns one.
355
+ """
321
356
  if event_type is None:
322
357
  event_type = self.scope['event_type']
323
- queryset = self.model.get_queryset(self.scope['user'], event_type, self.queryset)
324
-
325
- data_load_result = self.model.data_load(self.scope['user'], queryset, self.queryset)
358
+ if query is None:
359
+ query = self.queryset
360
+ clean_query = sanitize_filter_query(self.model, query)
361
+ if self.model_id is not None:
362
+ clean_query['pk'] = self.model_id
363
+ queryset = self.model.get_queryset(self.scope['user'], event_type, query)
364
+
365
+ data_load_result = self.model.data_load(self.scope['user'], queryset, clean_query)
326
366
  if data_load_result is not None:
327
367
  return data_load_result
328
- res = queryset.filter(**sanitize_filter_query(self.model, self.queryset))
329
- self.ids = list(res.values_list('id', flat=True))
368
+ return queryset.filter(**clean_query)
330
369
 
331
- return res
370
+ @staticmethod
371
+ def _pks(rows):
372
+ if hasattr(rows, 'values_list'):
373
+ return list(rows.values_list('pk', flat=True))
374
+ return [row.pk for row in rows]
375
+
376
+ def _track_ids(self, pks, replace=False):
377
+ """Remembers which rows the client has received (for on_update/on_delete)."""
378
+ if replace:
379
+ self.ids = list(pks)
380
+ else:
381
+ self.ids += [pk for pk in pks if pk not in self.ids]
382
+
383
+ def _full_load(self):
384
+ """Loads with the current filter and makes it the filter for live updates."""
385
+ self.live_query = dict(self.queryset)
386
+ rows = self._load_model()
387
+ self._track_ids(self._pks(rows), replace=True)
388
+ return rows
389
+
390
+ def _load(self, json_data):
391
+ # queryset.update({item}) loads a single row with {"id": <pk>}: that row is added to
392
+ # the known rows; any other load replaces them and the live filter.
393
+ if set(self.queryset.keys()) in ({'id'}, {'pk'}):
394
+ rows = self._load_model()
395
+ self._track_ids(self._pks(rows))
396
+ else:
397
+ rows = self._full_load()
398
+ self.send(text_data=json.dumps({
399
+ 'type': json_data['event_type'],
400
+ 'event_id': json_data['event_id'],
401
+ 'args': self._prepare_loaded_data(rows),
402
+ 'is_error': False
403
+ }))
332
404
 
333
405
  def _init_connection(self, json_data):
334
- self._load_model()
406
+ self._full_load()
335
407
  self._add_to_class()
336
408
  # self.model_description = {field.name: field.__class__.__name__ for field in self.model._meta.fields}
337
409
  self.send(text_data=json.dumps({
@@ -360,15 +432,7 @@ class SDCModelConsumer(WebsocketConsumer):
360
432
  return self._load_form(json_data, getattr(self.model.SdcMeta, form_name), instance)
361
433
 
362
434
  def _load_form(self, json_data, form_attr, instance=None):
363
- if callable(form_attr):
364
- form_attr = form_attr({})
365
- if form_attr is None:
366
- raise NotImplementedError()
367
- elif isinstance(form_attr, str):
368
- edit_form_path = form_attr.split('.')
369
- form = getattr(importlib.import_module('.'.join(edit_form_path[0:-1])), edit_form_path[-1])
370
- else:
371
- form = form_attr
435
+ form = resolve_form(form_attr)
372
436
 
373
437
  self.send(text_data=json.dumps({
374
438
  'type': json_data['event_type'],
@@ -422,13 +486,7 @@ class SDCModelConsumer(WebsocketConsumer):
422
486
  def _submit_element(self, json_data, form_attr, instance=None):
423
487
  data = json_data['args']['data']
424
488
  files = json_data['args'].get('files')
425
- if form_attr is None:
426
- raise NotImplementedError()
427
- elif isinstance(form_attr, str):
428
- edit_form_path = form_attr.split('.')
429
- form = getattr(importlib.import_module('.'.join(edit_form_path[0:-1])), edit_form_path[-1])
430
- else:
431
- form = form_attr
489
+ form = resolve_form(form_attr)
432
490
  uploads = MultiValueDict()
433
491
  if files is not None:
434
492
  for (key, val) in files.items():
@@ -442,6 +500,7 @@ class SDCModelConsumer(WebsocketConsumer):
442
500
  if is_valid:
443
501
  form_instance.save()
444
502
  new_instance = SDCSerializer().serialize(self._load_model().filter(pk=form_instance.instance.pk))
503
+ self._track_ids([form_instance.instance.pk])
445
504
  msg = self.msg_manager.get_msg(form_instance.instance, json_data['event_type'])
446
505
  else:
447
506
  msg = {'header': 'Upss!',
@@ -463,7 +522,7 @@ class SDCModelConsumer(WebsocketConsumer):
463
522
  if self.model.SdcMeta.html_list_template is None:
464
523
  raise NotImplementedError()
465
524
  filter = self.queryset.pop('__search_values', {})
466
- loaded_data = self._load_model()
525
+ loaded_data = self._full_load()
467
526
  self.send(text_data=json.dumps({
468
527
  'type': json_data['event_type'],
469
528
  'event_id': json_data['event_id'],
@@ -473,10 +532,11 @@ class SDCModelConsumer(WebsocketConsumer):
473
532
  }))
474
533
 
475
534
  def _load_named_view(self, json_data):
476
- loaded_data = self._load_model()
535
+ loaded_data = self._full_load()
477
536
  view_name = json_data['args']['view_name']
478
537
  if not hasattr(self.model.SdcMeta, view_name):
479
- raise NotImplementedError()
538
+ text = f'"{view_name}" is not implemented in "{loaded_data.__class__.__name__}.SdcMeta".'
539
+ raise Exception(text)
480
540
  self.send(text_data=json.dumps({
481
541
  'type': json_data['event_type'],
482
542
  'event_id': json_data['event_id'],
@@ -490,6 +550,7 @@ class SDCModelConsumer(WebsocketConsumer):
490
550
  raise NotImplementedError()
491
551
  instance = self._load_model().get(pk=json_data['args']['pk'])
492
552
  instance.scope = self.scope
553
+ self._track_ids([instance.pk])
493
554
  self.send(text_data=json.dumps({
494
555
  'type': json_data['event_type'],
495
556
  'event_id': json_data['event_id'],
@@ -60,6 +60,9 @@ def verify_jwt(token, type):
60
60
 
61
61
  user = User.objects.get(id=payload["user_id"])
62
62
 
63
+ if not user.is_active:
64
+ raise jwt.InvalidTokenError
65
+
63
66
  if type == 'refresh' and abs(payload['iat'] - user.last_login.timestamp()) > 10:
64
67
  raise jwt.InvalidTokenError
65
68
 
@@ -21,9 +21,10 @@ def add_sdc_to_main_urls(main_urls_path):
21
21
  new_apps = "%sre_path('sdc_view/sdc_tools/', include('sdc_tools.sdc_urls')),\n" % options.SEP
22
22
  new_apps += "%sre_path('sdc_view/sdc_user/', include('sdc_user.sdc_urls')),\n" % options.SEP
23
23
 
24
+ # The login route must come before sdc_api/<str:model>/, which would match "login" as well.
25
+ new_apps += f"{options.SEP}path('sdc_api/login/', get_api_token),\n"
24
26
  new_apps += f"{options.SEP}path('sdc_api/<str:model>/<int:id>/', AdcApi.as_view()),\n"
25
27
  new_apps += f"{options.SEP}path('sdc_api/<str:model>/', AdcApi.as_view()),\n"
26
- new_apps += f"{options.SEP}path('sdc_api/login/', get_api_token),\n"
27
28
 
28
29
  line = re.sub(r'urlpatterns = \[',
29
30
  "urlpatterns = [\n%s%s# scd view below\n" % (new_apps, options.SEP), line)
@@ -57,7 +57,6 @@ class Command(BaseCommand):
57
57
  if not self.check_snake_name(controller_name):
58
58
  exit(1)
59
59
  mixin_apps = ops.get('mixin_apps')
60
- print(mixin_apps)
61
60
  if mixin_apps is True:
62
61
  mixin_apps = []
63
62
  else: