arthexis 0.1.11__tar.gz → 0.1.13__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.

Potentially problematic release.


This version of arthexis might be problematic. Click here for more details.

Files changed (232) hide show
  1. {arthexis-0.1.11 → arthexis-0.1.13}/PKG-INFO +2 -2
  2. {arthexis-0.1.11 → arthexis-0.1.13}/README.md +1 -1
  3. {arthexis-0.1.11 → arthexis-0.1.13}/arthexis.egg-info/PKG-INFO +2 -2
  4. {arthexis-0.1.11 → arthexis-0.1.13}/arthexis.egg-info/SOURCES.txt +19 -1
  5. {arthexis-0.1.11 → arthexis-0.1.13}/config/asgi.py +15 -1
  6. {arthexis-0.1.11 → arthexis-0.1.13}/config/celery.py +8 -1
  7. {arthexis-0.1.11 → arthexis-0.1.13}/config/settings.py +49 -78
  8. arthexis-0.1.13/config/settings_helpers.py +109 -0
  9. {arthexis-0.1.11 → arthexis-0.1.13}/core/admin.py +293 -78
  10. {arthexis-0.1.11 → arthexis-0.1.13}/core/apps.py +21 -0
  11. {arthexis-0.1.11 → arthexis-0.1.13}/core/auto_upgrade.py +2 -2
  12. arthexis-0.1.13/core/form_fields.py +75 -0
  13. {arthexis-0.1.11 → arthexis-0.1.13}/core/models.py +203 -47
  14. {arthexis-0.1.11 → arthexis-0.1.13}/core/reference_utils.py +1 -1
  15. {arthexis-0.1.11 → arthexis-0.1.13}/core/release.py +42 -20
  16. {arthexis-0.1.11 → arthexis-0.1.13}/core/system.py +6 -3
  17. {arthexis-0.1.11 → arthexis-0.1.13}/core/tasks.py +92 -40
  18. {arthexis-0.1.11 → arthexis-0.1.13}/core/tests.py +75 -1
  19. {arthexis-0.1.11 → arthexis-0.1.13}/core/views.py +178 -29
  20. arthexis-0.1.13/core/widgets.py +94 -0
  21. arthexis-0.1.13/nodes/admin.py +1161 -0
  22. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/apps.py +15 -0
  23. arthexis-0.1.13/nodes/feature_checks.py +133 -0
  24. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/models.py +287 -49
  25. arthexis-0.1.13/nodes/reports.py +411 -0
  26. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/tests.py +990 -42
  27. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/urls.py +1 -0
  28. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/utils.py +32 -0
  29. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/views.py +173 -5
  30. arthexis-0.1.13/ocpp/admin.py +948 -0
  31. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/consumers.py +630 -15
  32. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/evcs.py +7 -94
  33. arthexis-0.1.13/ocpp/evcs_discovery.py +158 -0
  34. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/models.py +236 -4
  35. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/routing.py +4 -2
  36. arthexis-0.1.13/ocpp/simulator.py +745 -0
  37. arthexis-0.1.13/ocpp/status_display.py +26 -0
  38. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/store.py +110 -2
  39. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/tests.py +1425 -33
  40. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/transactions_io.py +27 -3
  41. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/views.py +344 -38
  42. {arthexis-0.1.11 → arthexis-0.1.13}/pages/admin.py +138 -3
  43. {arthexis-0.1.11 → arthexis-0.1.13}/pages/context_processors.py +15 -1
  44. {arthexis-0.1.11 → arthexis-0.1.13}/pages/defaults.py +1 -2
  45. {arthexis-0.1.11 → arthexis-0.1.13}/pages/forms.py +67 -0
  46. {arthexis-0.1.11 → arthexis-0.1.13}/pages/models.py +136 -1
  47. {arthexis-0.1.11 → arthexis-0.1.13}/pages/tests.py +379 -4
  48. {arthexis-0.1.11 → arthexis-0.1.13}/pages/urls.py +1 -0
  49. {arthexis-0.1.11 → arthexis-0.1.13}/pages/views.py +64 -7
  50. {arthexis-0.1.11 → arthexis-0.1.13}/pyproject.toml +1 -1
  51. arthexis-0.1.13/tests/test_api_login_required.py +58 -0
  52. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_assistant_profile_admin.py +4 -1
  53. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_auto_upgrade_scheduler.py +3 -3
  54. arthexis-0.1.13/tests/test_build_pypi_command.py +71 -0
  55. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_celery_no_debug.py +10 -1
  56. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_email_inbox.py +35 -0
  57. arthexis-0.1.13/tests/test_email_outbox_admin.py +124 -0
  58. arthexis-0.1.13/tests/test_invitation_login_view.py +125 -0
  59. arthexis-0.1.13/tests/test_manage_debug_flag.py +103 -0
  60. arthexis-0.1.13/tests/test_mcp_asgi.py +55 -0
  61. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_mcp_sigil_server.py +61 -1
  62. arthexis-0.1.13/tests/test_mcp_sigil_server_command.py +42 -0
  63. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_node_info_view.py +20 -0
  64. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_odoo_product.py +93 -3
  65. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_pypi_token.py +16 -10
  66. arthexis-0.1.13/tests/test_release_build_flow.py +68 -0
  67. arthexis-0.1.13/tests/test_release_logs.py +30 -0
  68. arthexis-0.1.13/tests/test_release_packages.py +31 -0
  69. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_release_progress.py +15 -11
  70. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_release_tasks.py +67 -12
  71. arthexis-0.1.13/tests/test_rfid_admin_scan_csrf.py +73 -0
  72. arthexis-0.1.13/tests/test_rfid_backend.py +63 -0
  73. arthexis-0.1.13/tests/test_settings_helpers.py +70 -0
  74. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_sigil_resolution.py +7 -2
  75. arthexis-0.1.13/tests/test_staff_required_decorator.py +57 -0
  76. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_temp_passwords.py +25 -0
  77. arthexis-0.1.13/tests/test_totp_admin.py +67 -0
  78. arthexis-0.1.13/tests/test_totp_backend.py +125 -0
  79. arthexis-0.1.13/tests/test_version_file.py +79 -0
  80. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_vscode_manage.py +16 -1
  81. arthexis-0.1.11/core/widgets.py +0 -51
  82. arthexis-0.1.11/nodes/admin.py +0 -588
  83. arthexis-0.1.11/ocpp/admin.py +0 -541
  84. arthexis-0.1.11/ocpp/simulator.py +0 -425
  85. arthexis-0.1.11/tests/test_email_outbox_admin.py +0 -43
  86. arthexis-0.1.11/tests/test_release_logs.py +0 -15
  87. arthexis-0.1.11/tests/test_rfid_admin_scan_csrf.py +0 -34
  88. arthexis-0.1.11/tests/test_scan_evcs_consoles_command.py +0 -103
  89. arthexis-0.1.11/tests/test_version_file.py +0 -23
  90. {arthexis-0.1.11 → arthexis-0.1.13}/LICENSE +0 -0
  91. {arthexis-0.1.11 → arthexis-0.1.13}/arthexis.egg-info/dependency_links.txt +0 -0
  92. {arthexis-0.1.11 → arthexis-0.1.13}/arthexis.egg-info/requires.txt +0 -0
  93. {arthexis-0.1.11 → arthexis-0.1.13}/arthexis.egg-info/top_level.txt +0 -0
  94. {arthexis-0.1.11 → arthexis-0.1.13}/config/__init__.py +0 -0
  95. {arthexis-0.1.11 → arthexis-0.1.13}/config/active_app.py +0 -0
  96. {arthexis-0.1.11 → arthexis-0.1.13}/config/auth_app.py +0 -0
  97. {arthexis-0.1.11 → arthexis-0.1.13}/config/context_processors.py +0 -0
  98. {arthexis-0.1.11 → arthexis-0.1.13}/config/horologia_app.py +0 -0
  99. {arthexis-0.1.11 → arthexis-0.1.13}/config/loadenv.py +0 -0
  100. {arthexis-0.1.11 → arthexis-0.1.13}/config/logging.py +0 -0
  101. {arthexis-0.1.11 → arthexis-0.1.13}/config/middleware.py +0 -0
  102. {arthexis-0.1.11 → arthexis-0.1.13}/config/offline.py +0 -0
  103. {arthexis-0.1.11 → arthexis-0.1.13}/config/urls.py +0 -0
  104. {arthexis-0.1.11 → arthexis-0.1.13}/config/wsgi.py +0 -0
  105. {arthexis-0.1.11/pages → arthexis-0.1.13/core}/__init__.py +0 -0
  106. {arthexis-0.1.11 → arthexis-0.1.13}/core/admin_history.py +0 -0
  107. {arthexis-0.1.11 → arthexis-0.1.13}/core/admindocs.py +0 -0
  108. {arthexis-0.1.11 → arthexis-0.1.13}/core/backends.py +0 -0
  109. {arthexis-0.1.11 → arthexis-0.1.13}/core/entity.py +0 -0
  110. {arthexis-0.1.11 → arthexis-0.1.13}/core/environment.py +0 -0
  111. {arthexis-0.1.11 → arthexis-0.1.13}/core/fields.py +0 -0
  112. {arthexis-0.1.11 → arthexis-0.1.13}/core/github_helper.py +0 -0
  113. {arthexis-0.1.11 → arthexis-0.1.13}/core/github_issues.py +0 -0
  114. {arthexis-0.1.11 → arthexis-0.1.13}/core/lcd_screen.py +0 -0
  115. {arthexis-0.1.11 → arthexis-0.1.13}/core/liveupdate.py +0 -0
  116. {arthexis-0.1.11 → arthexis-0.1.13}/core/log_paths.py +0 -0
  117. {arthexis-0.1.11 → arthexis-0.1.13}/core/mailer.py +0 -0
  118. {arthexis-0.1.11 → arthexis-0.1.13}/core/middleware.py +0 -0
  119. {arthexis-0.1.11 → arthexis-0.1.13}/core/notifications.py +0 -0
  120. {arthexis-0.1.11 → arthexis-0.1.13}/core/public_wifi.py +0 -0
  121. {arthexis-0.1.11 → arthexis-0.1.13}/core/sigil_builder.py +0 -0
  122. {arthexis-0.1.11 → arthexis-0.1.13}/core/sigil_context.py +0 -0
  123. {arthexis-0.1.11 → arthexis-0.1.13}/core/sigil_resolver.py +0 -0
  124. {arthexis-0.1.11 → arthexis-0.1.13}/core/temp_passwords.py +0 -0
  125. {arthexis-0.1.11 → arthexis-0.1.13}/core/test_system_info.py +0 -0
  126. {arthexis-0.1.11 → arthexis-0.1.13}/core/tests_liveupdate.py +0 -0
  127. {arthexis-0.1.11 → arthexis-0.1.13}/core/urls.py +0 -0
  128. {arthexis-0.1.11 → arthexis-0.1.13}/core/user_data.py +0 -0
  129. {arthexis-0.1.11 → arthexis-0.1.13}/core/workgroup_urls.py +0 -0
  130. {arthexis-0.1.11 → arthexis-0.1.13}/core/workgroup_views.py +0 -0
  131. {arthexis-0.1.11/ocpp → arthexis-0.1.13/nodes}/__init__.py +0 -0
  132. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/actions.py +0 -0
  133. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/backends.py +0 -0
  134. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/dns.py +0 -0
  135. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/lcd.py +0 -0
  136. {arthexis-0.1.11 → arthexis-0.1.13}/nodes/tasks.py +0 -0
  137. {arthexis-0.1.11/nodes → arthexis-0.1.13/ocpp}/__init__.py +0 -0
  138. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/apps.py +0 -0
  139. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/reference_utils.py +0 -0
  140. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/tasks.py +0 -0
  141. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/test_export_import.py +0 -0
  142. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/test_rfid.py +0 -0
  143. {arthexis-0.1.11 → arthexis-0.1.13}/ocpp/urls.py +0 -0
  144. {arthexis-0.1.11/core → arthexis-0.1.13/pages}/__init__.py +0 -0
  145. {arthexis-0.1.11 → arthexis-0.1.13}/pages/apps.py +0 -0
  146. {arthexis-0.1.11 → arthexis-0.1.13}/pages/checks.py +0 -0
  147. {arthexis-0.1.11 → arthexis-0.1.13}/pages/middleware.py +0 -0
  148. {arthexis-0.1.11 → arthexis-0.1.13}/pages/utils.py +0 -0
  149. {arthexis-0.1.11 → arthexis-0.1.13}/setup.cfg +0 -0
  150. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_acronym_capitalization.py +0 -0
  151. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_client_report.py +0 -0
  152. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_doc_commands.py +0 -0
  153. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_doc_model_groups.py +0 -0
  154. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_history.py +0 -0
  155. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_index_actions.py +0 -0
  156. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_model_graph.py +0 -0
  157. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_object_history.py +0 -0
  158. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_profile_link.py +0 -0
  159. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_admin_system_stop.py +0 -0
  160. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_allowed_hosts_hostname.py +0 -0
  161. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_assistant_data_api.py +0 -0
  162. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_assistant_profile_api.py +0 -0
  163. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_awg_admin.py +0 -0
  164. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_benchmark_command.py +0 -0
  165. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_birthday_greetings.py +0 -0
  166. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_check_migrations_script.py +0 -0
  167. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_client_report_generation.py +0 -0
  168. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_client_report_schedule.py +0 -0
  169. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_csrf_failure.py +0 -0
  170. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_csrf_origin_subnet.py +0 -0
  171. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_dist_cleanup.py +0 -0
  172. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_email_collector.py +0 -0
  173. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_email_inbox_admin.py +0 -0
  174. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_email_inbox_search_action.py +0 -0
  175. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_email_profiles.py +0 -0
  176. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_env_refresh_clean.py +0 -0
  177. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_env_refresh_pip.py +0 -0
  178. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_env_refresh_unlink.py +0 -0
  179. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_experience_admin_group.py +0 -0
  180. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_fixture_presence.py +0 -0
  181. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_footer_no_references.py +0 -0
  182. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_footer_presence.py +0 -0
  183. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_footer_render.py +0 -0
  184. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_git_checks.py +0 -0
  185. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_github_issue_reporting.py +0 -0
  186. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_install_script.py +0 -0
  187. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_language_switch.py +0 -0
  188. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_lcd_check_command.py +0 -0
  189. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_lcd_smbus2.py +0 -0
  190. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_localhost_admin_backend.py +0 -0
  191. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_login_view_no_site.py +0 -0
  192. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_manage_debug.py +0 -0
  193. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_manuals.py +0 -0
  194. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_mcp_process.py +0 -0
  195. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_message_command.py +0 -0
  196. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_migrations.py +0 -0
  197. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_model_verbose_name_capitalization.py +0 -0
  198. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_network_setup_interactive.py +0 -0
  199. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_notifications_fallback.py +0 -0
  200. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_notify_command.py +0 -0
  201. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_ocpp_session_lock.py +0 -0
  202. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_odoo_profile.py +0 -0
  203. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_odoo_profile_admin.py +0 -0
  204. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_offline.py +0 -0
  205. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_package_admin_next_release.py +0 -0
  206. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_power_admin_group.py +0 -0
  207. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_profile_inline_deletion.py +0 -0
  208. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_projects_rfid.py +0 -0
  209. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_readme_language.py +0 -0
  210. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_reference_qr_code.py +0 -0
  211. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_reference_transaction_uuid.py +0 -0
  212. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_register_site_apps_command.py +0 -0
  213. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_release_checklist.py +0 -0
  214. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_release_manager_admin.py +0 -0
  215. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_request_invite.py +0 -0
  216. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_rfid_admin_reference_clear.py +0 -0
  217. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_rfid_background_reader.py +0 -0
  218. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_rfid_client_report.py +0 -0
  219. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_role_marker_filtering.py +0 -0
  220. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_seed_data.py +0 -0
  221. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_send_invite_command.py +0 -0
  222. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_shell_scripts.py +0 -0
  223. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_show_leads_command.py +0 -0
  224. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_sigil_builder.py +0 -0
  225. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_sites_utils.py +0 -0
  226. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_staff_login_net_message.py +0 -0
  227. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_switch_role_script.py +0 -0
  228. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_uninstall_script.py +0 -0
  229. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_update_fixtures_command.py +0 -0
  230. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_urls_autodiscover.py +0 -0
  231. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_user_data_admin.py +0 -0
  232. {arthexis-0.1.11 → arthexis-0.1.13}/tests/test_version_endpoint.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: Django-based MESH system
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: GPL-3.0-only
@@ -115,7 +115,7 @@ Dynamic: license-file
115
115
 
116
116
  # Arthexis Constellation
117
117
 
118
- [![Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/coverage.svg)](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [![OCPP 1.6 Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)
118
+ [![Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/coverage.svg)](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [![OCPP 1.6 Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)](https://github.com/arthexis/arthexis/blob/main/docs/development/ocpp-user-manual.md)
119
119
 
120
120
  ## Purpose
121
121
 
@@ -1,6 +1,6 @@
1
1
  # Arthexis Constellation
2
2
 
3
- [![Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/coverage.svg)](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [![OCPP 1.6 Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)
3
+ [![Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/coverage.svg)](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [![OCPP 1.6 Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)](https://github.com/arthexis/arthexis/blob/main/docs/development/ocpp-user-manual.md)
4
4
 
5
5
  ## Purpose
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: Django-based MESH system
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: GPL-3.0-only
@@ -115,7 +115,7 @@ Dynamic: license-file
115
115
 
116
116
  # Arthexis Constellation
117
117
 
118
- [![Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/coverage.svg)](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [![OCPP 1.6 Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)
118
+ [![Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/coverage.svg)](https://github.com/arthexis/arthexis/actions/workflows/coverage.yml) [![OCPP 1.6 Coverage](https://raw.githubusercontent.com/arthexis/arthexis/main/ocpp_coverage.svg)](https://github.com/arthexis/arthexis/blob/main/docs/development/ocpp-user-manual.md)
119
119
 
120
120
  ## Purpose
121
121
 
@@ -18,6 +18,7 @@ config/logging.py
18
18
  config/middleware.py
19
19
  config/offline.py
20
20
  config/settings.py
21
+ config/settings_helpers.py
21
22
  config/urls.py
22
23
  config/wsgi.py
23
24
  core/__init__.py
@@ -30,6 +31,7 @@ core/backends.py
30
31
  core/entity.py
31
32
  core/environment.py
32
33
  core/fields.py
34
+ core/form_fields.py
33
35
  core/github_helper.py
34
36
  core/github_issues.py
35
37
  core/lcd_screen.py
@@ -63,8 +65,10 @@ nodes/admin.py
63
65
  nodes/apps.py
64
66
  nodes/backends.py
65
67
  nodes/dns.py
68
+ nodes/feature_checks.py
66
69
  nodes/lcd.py
67
70
  nodes/models.py
71
+ nodes/reports.py
68
72
  nodes/tasks.py
69
73
  nodes/tests.py
70
74
  nodes/urls.py
@@ -75,10 +79,12 @@ ocpp/admin.py
75
79
  ocpp/apps.py
76
80
  ocpp/consumers.py
77
81
  ocpp/evcs.py
82
+ ocpp/evcs_discovery.py
78
83
  ocpp/models.py
79
84
  ocpp/reference_utils.py
80
85
  ocpp/routing.py
81
86
  ocpp/simulator.py
87
+ ocpp/status_display.py
82
88
  ocpp/store.py
83
89
  ocpp/tasks.py
84
90
  ocpp/test_export_import.py
@@ -111,6 +117,7 @@ tests/test_admin_object_history.py
111
117
  tests/test_admin_profile_link.py
112
118
  tests/test_admin_system_stop.py
113
119
  tests/test_allowed_hosts_hostname.py
120
+ tests/test_api_login_required.py
114
121
  tests/test_assistant_data_api.py
115
122
  tests/test_assistant_profile_admin.py
116
123
  tests/test_assistant_profile_api.py
@@ -118,6 +125,7 @@ tests/test_auto_upgrade_scheduler.py
118
125
  tests/test_awg_admin.py
119
126
  tests/test_benchmark_command.py
120
127
  tests/test_birthday_greetings.py
128
+ tests/test_build_pypi_command.py
121
129
  tests/test_celery_no_debug.py
122
130
  tests/test_check_migrations_script.py
123
131
  tests/test_client_report_generation.py
@@ -142,15 +150,19 @@ tests/test_footer_render.py
142
150
  tests/test_git_checks.py
143
151
  tests/test_github_issue_reporting.py
144
152
  tests/test_install_script.py
153
+ tests/test_invitation_login_view.py
145
154
  tests/test_language_switch.py
146
155
  tests/test_lcd_check_command.py
147
156
  tests/test_lcd_smbus2.py
148
157
  tests/test_localhost_admin_backend.py
149
158
  tests/test_login_view_no_site.py
150
159
  tests/test_manage_debug.py
160
+ tests/test_manage_debug_flag.py
151
161
  tests/test_manuals.py
162
+ tests/test_mcp_asgi.py
152
163
  tests/test_mcp_process.py
153
164
  tests/test_mcp_sigil_server.py
165
+ tests/test_mcp_sigil_server_command.py
154
166
  tests/test_message_command.py
155
167
  tests/test_migrations.py
156
168
  tests/test_model_verbose_name_capitalization.py
@@ -172,28 +184,34 @@ tests/test_readme_language.py
172
184
  tests/test_reference_qr_code.py
173
185
  tests/test_reference_transaction_uuid.py
174
186
  tests/test_register_site_apps_command.py
187
+ tests/test_release_build_flow.py
175
188
  tests/test_release_checklist.py
176
189
  tests/test_release_logs.py
177
190
  tests/test_release_manager_admin.py
191
+ tests/test_release_packages.py
178
192
  tests/test_release_progress.py
179
193
  tests/test_release_tasks.py
180
194
  tests/test_request_invite.py
181
195
  tests/test_rfid_admin_reference_clear.py
182
196
  tests/test_rfid_admin_scan_csrf.py
197
+ tests/test_rfid_backend.py
183
198
  tests/test_rfid_background_reader.py
184
199
  tests/test_rfid_client_report.py
185
200
  tests/test_role_marker_filtering.py
186
- tests/test_scan_evcs_consoles_command.py
187
201
  tests/test_seed_data.py
188
202
  tests/test_send_invite_command.py
203
+ tests/test_settings_helpers.py
189
204
  tests/test_shell_scripts.py
190
205
  tests/test_show_leads_command.py
191
206
  tests/test_sigil_builder.py
192
207
  tests/test_sigil_resolution.py
193
208
  tests/test_sites_utils.py
194
209
  tests/test_staff_login_net_message.py
210
+ tests/test_staff_required_decorator.py
195
211
  tests/test_switch_role_script.py
196
212
  tests/test_temp_passwords.py
213
+ tests/test_totp_admin.py
214
+ tests/test_totp_backend.py
197
215
  tests/test_uninstall_script.py
198
216
  tests/test_update_fixtures_command.py
199
217
  tests/test_urls_autodiscover.py
@@ -9,21 +9,35 @@ https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
9
9
 
10
10
  import os
11
11
  from config.loadenv import loadenv
12
+ from typing import Any, Awaitable, Callable, Dict, MutableMapping
12
13
  from channels.auth import AuthMiddlewareStack
13
14
  from channels.routing import ProtocolTypeRouter, URLRouter
14
15
  from django.core.asgi import get_asgi_application
15
16
  import ocpp.routing
16
17
 
18
+ from core.mcp.asgi import application as mcp_application
19
+ from core.mcp.asgi import is_mcp_scope
20
+
17
21
  loadenv()
18
22
  os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
19
23
 
20
24
  django_asgi_app = get_asgi_application()
21
25
 
26
+ Scope = MutableMapping[str, Any]
27
+ Receive = Callable[[], Awaitable[Dict[str, Any]]]
28
+ Send = Callable[[Dict[str, Any]], Awaitable[None]]
29
+
22
30
  websocket_patterns = ocpp.routing.websocket_urlpatterns
23
31
 
32
+ async def http_application(scope: Scope, receive: Receive, send: Send) -> None:
33
+ if is_mcp_scope(scope):
34
+ await mcp_application(scope, receive, send)
35
+ else:
36
+ await django_asgi_app(scope, receive, send)
37
+
24
38
  application = ProtocolTypeRouter(
25
39
  {
26
- "http": django_asgi_app,
40
+ "http": http_application,
27
41
  "websocket": AuthMiddlewareStack(URLRouter(websocket_patterns)),
28
42
  }
29
43
  )
@@ -9,7 +9,14 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
9
9
 
10
10
  # When running on production-oriented nodes, avoid Celery debug mode.
11
11
  NODE_ROLE = os.environ.get("NODE_ROLE", "")
12
- if NODE_ROLE in {"Constellation", "Satellite"}:
12
+ PRODUCTION_ROLES = {
13
+ "constellation",
14
+ "satellite",
15
+ "control",
16
+ "terminal",
17
+ "gateway",
18
+ }
19
+ if NODE_ROLE.lower() in PRODUCTION_ROLES:
13
20
  for var in ["CELERY_TRACE_APP", "CELERY_DEBUG"]:
14
21
  os.environ.pop(var, None)
15
22
  os.environ.setdefault("CELERY_LOG_LEVEL", "INFO")
@@ -22,61 +22,24 @@ from celery.schedules import crontab
22
22
  from django.http import request as http_request
23
23
  from django.http.request import split_domain_port
24
24
  from django.middleware.csrf import CsrfViewMiddleware
25
- from django.core.exceptions import DisallowedHost
25
+ from django.core.exceptions import DisallowedHost, ImproperlyConfigured
26
26
  from django.contrib.sites import shortcuts as sites_shortcuts
27
27
  from django.contrib.sites.requests import RequestSite
28
- from django.core.management.utils import get_random_secret_key
29
28
  from urllib.parse import urlsplit
30
29
  import django.utils.encoding as encoding
31
30
 
31
+ from config.settings_helpers import (
32
+ extract_ip_from_host,
33
+ install_validate_host_with_subnets,
34
+ load_secret_key,
35
+ strip_ipv6_brackets,
36
+ )
37
+
32
38
  if not hasattr(encoding, "force_text"): # pragma: no cover - Django>=5 compatibility
33
39
  from django.utils.encoding import force_str
34
40
 
35
41
  encoding.force_text = force_str
36
-
37
-
38
-
39
- _original_validate_host = http_request.validate_host
40
-
41
-
42
- def _strip_ipv6_brackets(host: str) -> str:
43
- if host.startswith("[") and host.endswith("]"):
44
- return host[1:-1]
45
- return host
46
-
47
-
48
- def _extract_ip_from_host(host: str):
49
- """Return an :mod:`ipaddress` object for ``host`` when possible."""
50
-
51
- candidate = _strip_ipv6_brackets(host)
52
- try:
53
- return ipaddress.ip_address(candidate)
54
- except ValueError:
55
- domain, _port = split_domain_port(host)
56
- if domain and domain != host:
57
- candidate = _strip_ipv6_brackets(domain)
58
- try:
59
- return ipaddress.ip_address(candidate)
60
- except ValueError:
61
- return None
62
- return None
63
-
64
-
65
- def _validate_host_with_subnets(host, allowed_hosts):
66
- ip = _extract_ip_from_host(host)
67
- if ip is None:
68
- return _original_validate_host(host, allowed_hosts)
69
- for pattern in allowed_hosts:
70
- try:
71
- network = ipaddress.ip_network(pattern)
72
- except ValueError:
73
- continue
74
- if ip in network:
75
- return True
76
- return _original_validate_host(host, allowed_hosts)
77
-
78
-
79
- http_request.validate_host = _validate_host_with_subnets
42
+ install_validate_host_with_subnets()
80
43
 
81
44
  # Build paths inside the project like this: BASE_DIR / 'subdir'.
82
45
  BASE_DIR = Path(__file__).resolve().parent.parent
@@ -96,29 +59,7 @@ with contextlib.suppress(FileNotFoundError):
96
59
  # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
97
60
 
98
61
  # SECURITY WARNING: keep the secret key used in production secret!
99
-
100
-
101
- def _load_secret_key() -> str:
102
- for env_var in ("DJANGO_SECRET_KEY", "SECRET_KEY"):
103
- value = os.environ.get(env_var)
104
- if value:
105
- return value
106
-
107
- secret_file = BASE_DIR / "locks" / "django-secret.key"
108
- with contextlib.suppress(OSError):
109
- stored_key = secret_file.read_text(encoding="utf-8").strip()
110
- if stored_key:
111
- return stored_key
112
-
113
- generated_key = get_random_secret_key()
114
- with contextlib.suppress(OSError):
115
- secret_file.parent.mkdir(parents=True, exist_ok=True)
116
- secret_file.write_text(generated_key, encoding="utf-8")
117
-
118
- return generated_key
119
-
120
-
121
- SECRET_KEY = _load_secret_key()
62
+ SECRET_KEY = load_secret_key(BASE_DIR)
122
63
 
123
64
  # SECURITY WARNING: don't run with debug turned on in production!
124
65
 
@@ -258,7 +199,7 @@ def _normalize_origin_tuple(scheme: str | None, host: str) -> tuple[str, str, st
258
199
  if not scheme or scheme.lower() not in {"http", "https"}:
259
200
  return None
260
201
  domain, port = split_domain_port(host)
261
- normalized_host = _strip_ipv6_brackets(domain.strip().lower())
202
+ normalized_host = strip_ipv6_brackets(domain.strip().lower())
262
203
  if not normalized_host:
263
204
  return None
264
205
  normalized_port = port.strip() if isinstance(port, str) else port
@@ -325,13 +266,13 @@ def _origin_verified_with_subnets(self, request):
325
266
  if normalized_origin is None:
326
267
  return _original_origin_verified(self, request)
327
268
 
328
- origin_ip = _extract_ip_from_host(normalized_origin[1])
269
+ origin_ip = extract_ip_from_host(normalized_origin[1])
329
270
 
330
271
  for candidate in _candidate_origin_tuples(request, allowed_hosts):
331
272
  if candidate == normalized_origin:
332
273
  return True
333
274
 
334
- candidate_ip = _extract_ip_from_host(candidate[1])
275
+ candidate_ip = extract_ip_from_host(candidate[1])
335
276
  if origin_ip and candidate_ip:
336
277
  for pattern in allowed_hosts:
337
278
  try:
@@ -367,13 +308,13 @@ def _check_referer_with_forwarded(self, request):
367
308
  return _original_check_referer(self, request)
368
309
 
369
310
  allowed_hosts = _get_allowed_hosts()
370
- referer_ip = _extract_ip_from_host(normalized_referer[1])
311
+ referer_ip = extract_ip_from_host(normalized_referer[1])
371
312
 
372
313
  for candidate in _candidate_origin_tuples(request, allowed_hosts):
373
314
  if candidate == normalized_referer:
374
315
  return
375
316
 
376
- candidate_ip = _extract_ip_from_host(candidate[1])
317
+ candidate_ip = extract_ip_from_host(candidate[1])
377
318
  if referer_ip and candidate_ip:
378
319
  for pattern in allowed_hosts:
379
320
  try:
@@ -524,6 +465,7 @@ MCP_SIGIL_SERVER = {
524
465
  "required_scopes": ["sigils:read"],
525
466
  "issuer_url": os.environ.get("MCP_SIGIL_ISSUER_URL"),
526
467
  "resource_server_url": os.environ.get("MCP_SIGIL_RESOURCE_URL"),
468
+ "mount_path": os.environ.get("MCP_SIGIL_MOUNT_PATH"),
527
469
  }
528
470
 
529
471
 
@@ -544,8 +486,16 @@ OTP_TOTP_ISSUER = os.environ.get("OTP_TOTP_ISSUER", "Arthexis")
544
486
  # Database
545
487
  # https://docs.djangoproject.com/en/5.2/ref/settings/#databases
546
488
 
489
+ FORCED_DB_BACKEND = os.environ.get("ARTHEXIS_FORCE_DB_BACKEND", "").strip().lower()
490
+ if FORCED_DB_BACKEND and FORCED_DB_BACKEND not in {"sqlite", "postgres"}:
491
+ raise ImproperlyConfigured(
492
+ "ARTHEXIS_FORCE_DB_BACKEND must be 'sqlite' or 'postgres' when defined."
493
+ )
494
+
547
495
 
548
496
  def _postgres_available() -> bool:
497
+ if FORCED_DB_BACKEND == "sqlite":
498
+ return False
549
499
  try:
550
500
  import psycopg
551
501
  except Exception:
@@ -566,7 +516,15 @@ def _postgres_available() -> bool:
566
516
  return False
567
517
 
568
518
 
569
- if _postgres_available():
519
+ if FORCED_DB_BACKEND == "postgres":
520
+ _use_postgres = True
521
+ elif FORCED_DB_BACKEND == "sqlite":
522
+ _use_postgres = False
523
+ else:
524
+ _use_postgres = _postgres_available()
525
+
526
+
527
+ if _use_postgres:
570
528
  DATABASES = {
571
529
  "default": {
572
530
  "ENGINE": "django.db.backends.postgresql",
@@ -582,10 +540,16 @@ if _postgres_available():
582
540
  }
583
541
  }
584
542
  else:
543
+ _sqlite_override = os.environ.get("ARTHEXIS_SQLITE_PATH")
544
+ if _sqlite_override:
545
+ SQLITE_DB_PATH = Path(_sqlite_override)
546
+ else:
547
+ SQLITE_DB_PATH = BASE_DIR / "db.sqlite3"
548
+
585
549
  DATABASES = {
586
550
  "default": {
587
551
  "ENGINE": "django.db.backends.sqlite3",
588
- "NAME": BASE_DIR / "db.sqlite3",
552
+ "NAME": SQLITE_DB_PATH,
589
553
  "OPTIONS": {"timeout": 60},
590
554
  "TEST": {"NAME": BASE_DIR / "test_db.sqlite3"},
591
555
  }
@@ -625,6 +589,8 @@ LANGUAGES = [
625
589
 
626
590
  LOCALE_PATHS = [BASE_DIR / "locale"]
627
591
 
592
+ FORMAT_MODULE_PATH = ["config.formats"]
593
+
628
594
  TIME_ZONE = "America/Monterrey"
629
595
 
630
596
  USE_I18N = True
@@ -685,10 +651,15 @@ LOGGING = {
685
651
  "backupCount": 7,
686
652
  "encoding": "utf-8",
687
653
  "formatter": "standard",
688
- }
654
+ },
655
+ "console": {
656
+ "class": "logging.StreamHandler",
657
+ "level": "ERROR",
658
+ "formatter": "standard",
659
+ },
689
660
  },
690
661
  "root": {
691
- "handlers": ["file"],
662
+ "handlers": ["file", "console"],
692
663
  "level": "DEBUG",
693
664
  },
694
665
  }
@@ -0,0 +1,109 @@
1
+ """Utility helpers shared by :mod:`config.settings` and related tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import ipaddress
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Mapping, MutableMapping
10
+
11
+ from django.core.management.utils import get_random_secret_key
12
+ from django.http import request as http_request
13
+ from django.http.request import split_domain_port
14
+
15
+
16
+ __all__ = [
17
+ "extract_ip_from_host",
18
+ "install_validate_host_with_subnets",
19
+ "load_secret_key",
20
+ "strip_ipv6_brackets",
21
+ "validate_host_with_subnets",
22
+ ]
23
+
24
+
25
+ def strip_ipv6_brackets(host: str) -> str:
26
+ """Return ``host`` without IPv6 URL literal brackets."""
27
+
28
+ if host.startswith("[") and host.endswith("]"):
29
+ return host[1:-1]
30
+ return host
31
+
32
+
33
+ def extract_ip_from_host(host: str):
34
+ """Return an :mod:`ipaddress` object for ``host`` when possible."""
35
+
36
+ candidate = strip_ipv6_brackets(host)
37
+ try:
38
+ return ipaddress.ip_address(candidate)
39
+ except ValueError:
40
+ domain, _port = split_domain_port(host)
41
+ if domain and domain != host:
42
+ candidate = strip_ipv6_brackets(domain)
43
+ try:
44
+ return ipaddress.ip_address(candidate)
45
+ except ValueError:
46
+ return None
47
+ return None
48
+
49
+
50
+ def validate_host_with_subnets(host, allowed_hosts, original_validate=None):
51
+ """Extend Django's host validation to honor subnet CIDR notation."""
52
+
53
+ if original_validate is None:
54
+ original_validate = http_request.validate_host
55
+
56
+ ip = extract_ip_from_host(host)
57
+ if ip is None:
58
+ return original_validate(host, allowed_hosts)
59
+
60
+ for pattern in allowed_hosts:
61
+ try:
62
+ network = ipaddress.ip_network(pattern)
63
+ except ValueError:
64
+ continue
65
+ if ip in network:
66
+ return True
67
+ return original_validate(host, allowed_hosts)
68
+
69
+
70
+ def install_validate_host_with_subnets() -> None:
71
+ """Monkeypatch Django's host validator to recognize subnet patterns."""
72
+
73
+ original_validate = http_request.validate_host
74
+
75
+ def _patched(host, allowed_hosts):
76
+ return validate_host_with_subnets(host, allowed_hosts, original_validate)
77
+
78
+ http_request.validate_host = _patched
79
+
80
+
81
+ def load_secret_key(
82
+ base_dir: Path,
83
+ env: Mapping[str, str] | MutableMapping[str, str] | None = None,
84
+ secret_file: Path | None = None,
85
+ ) -> str:
86
+ """Load the Django secret key from the environment or a persisted file."""
87
+
88
+ if env is None:
89
+ env = os.environ
90
+
91
+ for env_var in ("DJANGO_SECRET_KEY", "SECRET_KEY"):
92
+ value = env.get(env_var)
93
+ if value:
94
+ return value
95
+
96
+ if secret_file is None:
97
+ secret_file = base_dir / "locks" / "django-secret.key"
98
+
99
+ with contextlib.suppress(OSError):
100
+ stored_key = secret_file.read_text(encoding="utf-8").strip()
101
+ if stored_key:
102
+ return stored_key
103
+
104
+ generated_key = get_random_secret_key()
105
+ with contextlib.suppress(OSError):
106
+ secret_file.parent.mkdir(parents=True, exist_ok=True)
107
+ secret_file.write_text(generated_key, encoding="utf-8")
108
+
109
+ return generated_key