arthexis 0.1.15__tar.gz → 0.1.17__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 (248) hide show
  1. {arthexis-0.1.15 → arthexis-0.1.17}/PKG-INFO +1 -2
  2. {arthexis-0.1.15 → arthexis-0.1.17}/README.md +0 -1
  3. {arthexis-0.1.15 → arthexis-0.1.17}/arthexis.egg-info/PKG-INFO +1 -2
  4. {arthexis-0.1.15 → arthexis-0.1.17}/arthexis.egg-info/SOURCES.txt +2 -1
  5. {arthexis-0.1.15 → arthexis-0.1.17}/config/settings.py +3 -0
  6. {arthexis-0.1.15 → arthexis-0.1.17}/config/urls.py +5 -0
  7. {arthexis-0.1.15 → arthexis-0.1.17}/core/admin.py +242 -15
  8. {arthexis-0.1.15 → arthexis-0.1.17}/core/admindocs.py +44 -3
  9. {arthexis-0.1.15 → arthexis-0.1.17}/core/apps.py +1 -1
  10. {arthexis-0.1.15 → arthexis-0.1.17}/core/backends.py +46 -8
  11. {arthexis-0.1.15 → arthexis-0.1.17}/core/changelog.py +66 -5
  12. {arthexis-0.1.15 → arthexis-0.1.17}/core/github_issues.py +12 -7
  13. {arthexis-0.1.15 → arthexis-0.1.17}/core/mailer.py +9 -5
  14. {arthexis-0.1.15 → arthexis-0.1.17}/core/models.py +121 -29
  15. {arthexis-0.1.15 → arthexis-0.1.17}/core/release.py +107 -2
  16. {arthexis-0.1.15 → arthexis-0.1.17}/core/system.py +209 -2
  17. {arthexis-0.1.15 → arthexis-0.1.17}/core/tasks.py +5 -7
  18. {arthexis-0.1.15 → arthexis-0.1.17}/core/test_system_info.py +16 -0
  19. {arthexis-0.1.15 → arthexis-0.1.17}/core/tests.py +329 -0
  20. {arthexis-0.1.15 → arthexis-0.1.17}/core/views.py +279 -40
  21. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/admin.py +25 -1
  22. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/models.py +70 -4
  23. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/rfid_sync.py +15 -0
  24. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/tests.py +119 -0
  25. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/utils.py +3 -0
  26. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/admin.py +92 -10
  27. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/consumers.py +38 -0
  28. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/models.py +19 -4
  29. arthexis-0.1.17/ocpp/tasks.py +185 -0
  30. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/test_rfid.py +92 -5
  31. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/tests.py +243 -1
  32. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/views.py +23 -5
  33. {arthexis-0.1.15 → arthexis-0.1.17}/pages/admin.py +126 -4
  34. {arthexis-0.1.15 → arthexis-0.1.17}/pages/context_processors.py +20 -1
  35. {arthexis-0.1.15 → arthexis-0.1.17}/pages/models.py +3 -1
  36. arthexis-0.1.17/pages/module_defaults.py +156 -0
  37. {arthexis-0.1.15 → arthexis-0.1.17}/pages/tests.py +241 -8
  38. {arthexis-0.1.15 → arthexis-0.1.17}/pages/urls.py +1 -0
  39. {arthexis-0.1.15 → arthexis-0.1.17}/pages/views.py +61 -4
  40. {arthexis-0.1.15 → arthexis-0.1.17}/pyproject.toml +1 -1
  41. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_doc_model_groups.py +7 -0
  42. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_changelog_builder.py +124 -1
  43. arthexis-0.1.17/tests/test_check_admin_command.py +120 -0
  44. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_email_inbox_admin.py +0 -4
  45. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_localhost_admin_backend.py +22 -0
  46. arthexis-0.1.17/tests/test_login_view_no_site.py +49 -0
  47. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_mcp_auto_start.py +15 -0
  48. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_pypi_token.py +9 -4
  49. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_build_flow.py +151 -31
  50. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_manager_admin.py +16 -12
  51. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_progress.py +338 -32
  52. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_progress_pre_release_integration.py +19 -2
  53. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_rfid_admin_print_labels.py +22 -0
  54. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_rfid_admin_scan_csrf.py +67 -0
  55. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_rfid_backend.py +33 -0
  56. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_seed_data.py +115 -0
  57. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_sites_utils.py +25 -0
  58. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_totp_backend.py +21 -2
  59. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_upgrade_report.py +64 -1
  60. arthexis-0.1.15/ocpp/tasks.py +0 -31
  61. arthexis-0.1.15/tests/test_login_view_no_site.py +0 -27
  62. arthexis-0.1.15/tests/test_manage_debug.py +0 -31
  63. {arthexis-0.1.15 → arthexis-0.1.17}/LICENSE +0 -0
  64. {arthexis-0.1.15 → arthexis-0.1.17}/arthexis.egg-info/dependency_links.txt +0 -0
  65. {arthexis-0.1.15 → arthexis-0.1.17}/arthexis.egg-info/requires.txt +0 -0
  66. {arthexis-0.1.15 → arthexis-0.1.17}/arthexis.egg-info/top_level.txt +0 -0
  67. {arthexis-0.1.15 → arthexis-0.1.17}/config/__init__.py +0 -0
  68. {arthexis-0.1.15 → arthexis-0.1.17}/config/active_app.py +0 -0
  69. {arthexis-0.1.15 → arthexis-0.1.17}/config/asgi.py +0 -0
  70. {arthexis-0.1.15 → arthexis-0.1.17}/config/auth_app.py +0 -0
  71. {arthexis-0.1.15 → arthexis-0.1.17}/config/celery.py +0 -0
  72. {arthexis-0.1.15 → arthexis-0.1.17}/config/context_processors.py +0 -0
  73. {arthexis-0.1.15 → arthexis-0.1.17}/config/horologia_app.py +0 -0
  74. {arthexis-0.1.15 → arthexis-0.1.17}/config/loadenv.py +0 -0
  75. {arthexis-0.1.15 → arthexis-0.1.17}/config/logging.py +0 -0
  76. {arthexis-0.1.15 → arthexis-0.1.17}/config/middleware.py +0 -0
  77. {arthexis-0.1.15 → arthexis-0.1.17}/config/offline.py +0 -0
  78. {arthexis-0.1.15 → arthexis-0.1.17}/config/settings_helpers.py +0 -0
  79. {arthexis-0.1.15 → arthexis-0.1.17}/config/wsgi.py +0 -0
  80. {arthexis-0.1.15 → arthexis-0.1.17}/core/__init__.py +0 -0
  81. {arthexis-0.1.15 → arthexis-0.1.17}/core/admin_history.py +0 -0
  82. {arthexis-0.1.15 → arthexis-0.1.17}/core/auto_upgrade.py +0 -0
  83. {arthexis-0.1.15 → arthexis-0.1.17}/core/entity.py +0 -0
  84. {arthexis-0.1.15 → arthexis-0.1.17}/core/environment.py +0 -0
  85. {arthexis-0.1.15 → arthexis-0.1.17}/core/fields.py +0 -0
  86. {arthexis-0.1.15 → arthexis-0.1.17}/core/form_fields.py +0 -0
  87. {arthexis-0.1.15 → arthexis-0.1.17}/core/github_helper.py +0 -0
  88. {arthexis-0.1.15 → arthexis-0.1.17}/core/github_repos.py +0 -0
  89. {arthexis-0.1.15 → arthexis-0.1.17}/core/lcd_screen.py +0 -0
  90. {arthexis-0.1.15 → arthexis-0.1.17}/core/liveupdate.py +0 -0
  91. {arthexis-0.1.15 → arthexis-0.1.17}/core/log_paths.py +0 -0
  92. {arthexis-0.1.15 → arthexis-0.1.17}/core/middleware.py +0 -0
  93. {arthexis-0.1.15 → arthexis-0.1.17}/core/notifications.py +0 -0
  94. {arthexis-0.1.15 → arthexis-0.1.17}/core/public_wifi.py +0 -0
  95. {arthexis-0.1.15 → arthexis-0.1.17}/core/reference_utils.py +0 -0
  96. {arthexis-0.1.15 → arthexis-0.1.17}/core/rfid_import_export.py +0 -0
  97. {arthexis-0.1.15 → arthexis-0.1.17}/core/sigil_builder.py +0 -0
  98. {arthexis-0.1.15 → arthexis-0.1.17}/core/sigil_context.py +0 -0
  99. {arthexis-0.1.15 → arthexis-0.1.17}/core/sigil_resolver.py +0 -0
  100. {arthexis-0.1.15 → arthexis-0.1.17}/core/temp_passwords.py +0 -0
  101. {arthexis-0.1.15 → arthexis-0.1.17}/core/tests_liveupdate.py +0 -0
  102. {arthexis-0.1.15 → arthexis-0.1.17}/core/urls.py +0 -0
  103. {arthexis-0.1.15 → arthexis-0.1.17}/core/user_data.py +0 -0
  104. {arthexis-0.1.15 → arthexis-0.1.17}/core/widgets.py +0 -0
  105. {arthexis-0.1.15 → arthexis-0.1.17}/core/workgroup_urls.py +0 -0
  106. {arthexis-0.1.15 → arthexis-0.1.17}/core/workgroup_views.py +0 -0
  107. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/__init__.py +0 -0
  108. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/apps.py +0 -0
  109. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/backends.py +0 -0
  110. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/dns.py +0 -0
  111. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/feature_checks.py +0 -0
  112. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/lcd.py +0 -0
  113. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/reports.py +0 -0
  114. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/signals.py +0 -0
  115. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/tasks.py +0 -0
  116. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/urls.py +0 -0
  117. {arthexis-0.1.15 → arthexis-0.1.17}/nodes/views.py +0 -0
  118. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/__init__.py +0 -0
  119. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/apps.py +0 -0
  120. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/evcs.py +0 -0
  121. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/evcs_discovery.py +0 -0
  122. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/reference_utils.py +0 -0
  123. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/routing.py +0 -0
  124. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/simulator.py +0 -0
  125. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/status_display.py +0 -0
  126. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/store.py +0 -0
  127. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/test_export_import.py +0 -0
  128. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/transactions_io.py +0 -0
  129. {arthexis-0.1.15 → arthexis-0.1.17}/ocpp/urls.py +0 -0
  130. {arthexis-0.1.15 → arthexis-0.1.17}/pages/__init__.py +0 -0
  131. {arthexis-0.1.15 → arthexis-0.1.17}/pages/apps.py +0 -0
  132. {arthexis-0.1.15 → arthexis-0.1.17}/pages/checks.py +0 -0
  133. {arthexis-0.1.15 → arthexis-0.1.17}/pages/defaults.py +0 -0
  134. {arthexis-0.1.15 → arthexis-0.1.17}/pages/forms.py +0 -0
  135. {arthexis-0.1.15 → arthexis-0.1.17}/pages/middleware.py +0 -0
  136. {arthexis-0.1.15 → arthexis-0.1.17}/pages/tasks.py +0 -0
  137. {arthexis-0.1.15 → arthexis-0.1.17}/pages/utils.py +0 -0
  138. {arthexis-0.1.15 → arthexis-0.1.17}/setup.cfg +0 -0
  139. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_acronym_capitalization.py +0 -0
  140. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_client_report.py +0 -0
  141. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_doc_commands.py +0 -0
  142. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_history.py +0 -0
  143. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_index_actions.py +0 -0
  144. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_model_graph.py +0 -0
  145. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_object_history.py +0 -0
  146. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_profile_link.py +0 -0
  147. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_admin_system_stop.py +0 -0
  148. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_allowed_hosts_hostname.py +0 -0
  149. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_api_login_required.py +0 -0
  150. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_assistant_data_api.py +0 -0
  151. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_assistant_profile_admin.py +0 -0
  152. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_assistant_profile_api.py +0 -0
  153. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_auto_upgrade_scheduler.py +0 -0
  154. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_awg_admin.py +0 -0
  155. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_benchmark_command.py +0 -0
  156. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_birthday_greetings.py +0 -0
  157. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_build_pypi_command.py +0 -0
  158. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_celery_no_debug.py +0 -0
  159. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_check_migrations_script.py +0 -0
  160. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_check_pypi_command.py +0 -0
  161. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_clean_release_logs_command.py +0 -0
  162. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_client_report_generation.py +0 -0
  163. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_client_report_schedule.py +0 -0
  164. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_csrf_failure.py +0 -0
  165. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_csrf_origin_subnet.py +0 -0
  166. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_dist_cleanup.py +0 -0
  167. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_email_collector.py +0 -0
  168. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_email_inbox.py +0 -0
  169. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_email_inbox_search_action.py +0 -0
  170. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_email_outbox_admin.py +0 -0
  171. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_email_profiles.py +0 -0
  172. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_email_transaction.py +0 -0
  173. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_env_refresh_clean.py +0 -0
  174. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_env_refresh_pip.py +0 -0
  175. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_env_refresh_unlink.py +0 -0
  176. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_experience_admin_group.py +0 -0
  177. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_fixture_presence.py +0 -0
  178. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_footer_no_references.py +0 -0
  179. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_footer_presence.py +0 -0
  180. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_footer_render.py +0 -0
  181. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_git_checks.py +0 -0
  182. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_github_issue_reporting.py +0 -0
  183. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_install_script.py +0 -0
  184. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_invitation_login_view.py +0 -0
  185. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_language_switch.py +0 -0
  186. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_lcd_check_command.py +0 -0
  187. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_lcd_smbus2.py +0 -0
  188. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_log_paths.py +0 -0
  189. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_manage_debug_flag.py +0 -0
  190. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_manuals.py +0 -0
  191. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_mcp_asgi.py +0 -0
  192. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_mcp_process.py +0 -0
  193. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_mcp_sigil_server.py +0 -0
  194. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_mcp_sigil_server_command.py +0 -0
  195. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_message_command.py +0 -0
  196. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_migrations.py +0 -0
  197. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_model_verbose_name_capitalization.py +0 -0
  198. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_network_setup_interactive.py +0 -0
  199. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_node_info_view.py +0 -0
  200. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_notifications_fallback.py +0 -0
  201. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_notify_command.py +0 -0
  202. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_ocpp_session_lock.py +0 -0
  203. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_odoo_product.py +0 -0
  204. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_odoo_profile.py +0 -0
  205. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_odoo_profile_admin.py +0 -0
  206. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_odoo_quote_report.py +0 -0
  207. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_offline.py +0 -0
  208. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_package_admin_next_release.py +0 -0
  209. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_power_admin_group.py +0 -0
  210. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_profile_inline_deletion.py +0 -0
  211. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_pypi_check.py +0 -0
  212. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_readme_language.py +0 -0
  213. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_reference_qr_code.py +0 -0
  214. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_reference_transaction_uuid.py +0 -0
  215. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_register_site_apps_command.py +0 -0
  216. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_build.py +0 -0
  217. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_checklist.py +0 -0
  218. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_logs.py +0 -0
  219. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_packages.py +0 -0
  220. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_push.py +0 -0
  221. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_release_tasks.py +0 -0
  222. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_request_invite.py +0 -0
  223. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_rfid_admin_reference_clear.py +0 -0
  224. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_rfid_always_on.py +0 -0
  225. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_rfid_background_reader.py +0 -0
  226. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_rfid_client_report.py +0 -0
  227. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_rfid_watch_command.py +0 -0
  228. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_role_marker_filtering.py +0 -0
  229. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_send_invite_command.py +0 -0
  230. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_settings_helpers.py +0 -0
  231. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_shell_scripts.py +0 -0
  232. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_show_leads_command.py +0 -0
  233. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_sigil_builder.py +0 -0
  234. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_sigil_resolution.py +0 -0
  235. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_social_profile.py +0 -0
  236. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_staff_login_net_message.py +0 -0
  237. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_staff_required_decorator.py +0 -0
  238. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_switch_role_script.py +0 -0
  239. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_system_changelog_report.py +0 -0
  240. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_temp_passwords.py +0 -0
  241. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_totp_admin.py +0 -0
  242. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_uninstall_script.py +0 -0
  243. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_update_fixtures_command.py +0 -0
  244. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_urls_autodiscover.py +0 -0
  245. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_user_data_admin.py +0 -0
  246. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_version_endpoint.py +0 -0
  247. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_version_file.py +0 -0
  248. {arthexis-0.1.15 → arthexis-0.1.17}/tests/test_vscode_manage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: Power & Energy Infrastructure
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: GPL-3.0-only
@@ -118,7 +118,6 @@ Dynamic: license-file
118
118
 
119
119
  [![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)
120
120
 
121
- For coding guidance, see [AGENTS.md](AGENTS.md).
122
121
 
123
122
  ## Purpose
124
123
 
@@ -2,7 +2,6 @@
2
2
 
3
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
- For coding guidance, see [AGENTS.md](AGENTS.md).
6
5
 
7
6
  ## Purpose
8
7
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: Power & Energy Infrastructure
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: GPL-3.0-only
@@ -118,7 +118,6 @@ Dynamic: license-file
118
118
 
119
119
  [![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)
120
120
 
121
- For coding guidance, see [AGENTS.md](AGENTS.md).
122
121
 
123
122
  ## Purpose
124
123
 
@@ -106,6 +106,7 @@ pages/defaults.py
106
106
  pages/forms.py
107
107
  pages/middleware.py
108
108
  pages/models.py
109
+ pages/module_defaults.py
109
110
  pages/tasks.py
110
111
  pages/tests.py
111
112
  pages/urls.py
@@ -133,6 +134,7 @@ tests/test_birthday_greetings.py
133
134
  tests/test_build_pypi_command.py
134
135
  tests/test_celery_no_debug.py
135
136
  tests/test_changelog_builder.py
137
+ tests/test_check_admin_command.py
136
138
  tests/test_check_migrations_script.py
137
139
  tests/test_check_pypi_command.py
138
140
  tests/test_clean_release_logs_command.py
@@ -166,7 +168,6 @@ tests/test_lcd_smbus2.py
166
168
  tests/test_localhost_admin_backend.py
167
169
  tests/test_log_paths.py
168
170
  tests/test_login_view_no_site.py
169
- tests/test_manage_debug.py
170
171
  tests/test_manage_debug_flag.py
171
172
  tests/test_manuals.py
172
173
  tests/test_mcp_asgi.py
@@ -480,6 +480,9 @@ AUTHENTICATION_BACKENDS = [
480
480
  "core.backends.RFIDBackend",
481
481
  ]
482
482
 
483
+ # Use the custom login view for all authentication redirects.
484
+ LOGIN_URL = "pages:login"
485
+
483
486
  # Issuer name used when generating otpauth URLs for authenticator apps.
484
487
  OTP_TOTP_ISSUER = os.environ.get("OTP_TOTP_ISSUER", "Arthexis")
485
488
 
@@ -134,6 +134,11 @@ urlpatterns = [
134
134
  core_views.todo_done,
135
135
  name="todo-done",
136
136
  ),
137
+ path(
138
+ "admin/core/todos/<int:pk>/snapshot/",
139
+ core_views.todo_snapshot,
140
+ name="todo-snapshot",
141
+ ),
137
142
  path(
138
143
  "admin/core/odoo-products/",
139
144
  core_views.odoo_products,
@@ -1,4 +1,5 @@
1
1
  from io import BytesIO
2
+ import os
2
3
 
3
4
  from django import forms
4
5
  from django.contrib import admin
@@ -34,7 +35,8 @@ from import_export.forms import (
34
35
  from import_export.widgets import ForeignKeyWidget
35
36
  from django.contrib.auth.models import Group
36
37
  from django.templatetags.static import static
37
- from django.utils import timezone
38
+ from django.utils import timezone, translation
39
+ from django.utils.formats import date_format
38
40
  from django.utils.dateparse import parse_datetime
39
41
  from django.utils.html import format_html
40
42
  from django.utils.translation import gettext_lazy as _, ngettext
@@ -55,6 +57,8 @@ from reportlab.pdfbase import pdfmetrics
55
57
  from reportlab.graphics import renderPDF
56
58
  from reportlab.graphics.barcode import qr
57
59
  from reportlab.graphics.shapes import Drawing
60
+ from reportlab.lib.styles import getSampleStyleSheet
61
+ from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
58
62
  from ocpp.models import Transaction
59
63
  from ocpp.rfid.utils import build_mode_toggle
60
64
  from nodes.models import EmailOutbox
@@ -515,22 +519,39 @@ class ReleaseManagerAdmin(ProfileAdminMixin, SaveBeforeChangeAction, EntityModel
515
519
  if not creds:
516
520
  self.message_user(request, f"{manager} has no credentials", messages.ERROR)
517
521
  return
518
- url = manager.pypi_url or "https://upload.pypi.org/legacy/"
522
+ env_url = os.environ.get("PYPI_REPOSITORY_URL", "").strip()
523
+ url = env_url or "https://upload.pypi.org/legacy/"
519
524
  auth = (
520
525
  ("__token__", creds.token)
521
526
  if creds.token
522
527
  else (creds.username, creds.password)
523
528
  )
524
529
  try:
525
- resp = requests.get(url, auth=auth, timeout=10)
526
- if resp.ok:
530
+ resp = requests.post(
531
+ url,
532
+ auth=auth,
533
+ data={"verify_credentials": "1"},
534
+ timeout=10,
535
+ allow_redirects=False,
536
+ )
537
+ status = resp.status_code
538
+ if status in {401, 403}:
539
+ self.message_user(
540
+ request,
541
+ f"{manager} credentials invalid ({status})",
542
+ messages.ERROR,
543
+ )
544
+ elif status <= 400:
545
+ suffix = f" ({status})" if status != 200 else ""
527
546
  self.message_user(
528
- request, f"{manager} credentials valid", messages.SUCCESS
547
+ request,
548
+ f"{manager} credentials valid{suffix}",
549
+ messages.SUCCESS,
529
550
  )
530
551
  else:
531
552
  self.message_user(
532
553
  request,
533
- f"{manager} credentials invalid ({resp.status_code})",
554
+ f"{manager} credentials check returned unexpected status {status}",
534
555
  messages.ERROR,
535
556
  )
536
557
  except Exception as exc: # pragma: no cover - admin feedback
@@ -1288,11 +1309,11 @@ class AssistantProfileInlineForm(ProfileFormMixin, forms.ModelForm):
1288
1309
  widget=forms.PasswordInput(render_value=True),
1289
1310
  help_text="Provide a plain key to create or rotate credentials.",
1290
1311
  )
1291
- profile_fields = ("user_key", "scopes", "is_active")
1312
+ profile_fields = ("assistant_name", "user_key", "scopes", "is_active")
1292
1313
 
1293
1314
  class Meta:
1294
1315
  model = AssistantProfile
1295
- fields = ("scopes", "is_active")
1316
+ fields = ("assistant_name", "scopes", "is_active")
1296
1317
 
1297
1318
  def __init__(self, *args, **kwargs):
1298
1319
  super().__init__(*args, **kwargs)
@@ -1454,7 +1475,7 @@ PROFILE_INLINE_CONFIG = {
1454
1475
  },
1455
1476
  AssistantProfile: {
1456
1477
  "form": AssistantProfileInlineForm,
1457
- "fields": ("user_key", "scopes", "is_active"),
1478
+ "fields": ("assistant_name", "user_key", "scopes", "is_active"),
1458
1479
  "readonly_fields": ("user_key_hash", "created_at", "last_used_at"),
1459
1480
  "template": "admin/edit_inline/profile_stacked.html",
1460
1481
  },
@@ -1646,6 +1667,7 @@ class UserAdmin(UserDatumAdminMixin, DjangoUserAdmin):
1646
1667
  class EmailCollectorInline(admin.TabularInline):
1647
1668
  model = EmailCollector
1648
1669
  extra = 0
1670
+ fields = ("name", "subject", "sender")
1649
1671
 
1650
1672
 
1651
1673
  class EmailCollectorAdmin(EntityModelAdmin):
@@ -1994,7 +2016,7 @@ class EmailInboxAdmin(ProfileAdminMixin, SaveBeforeChangeAction, EntityModelAdmi
1994
2016
  class AssistantProfileAdmin(
1995
2017
  ProfileAdminMixin, SaveBeforeChangeAction, EntityModelAdmin
1996
2018
  ):
1997
- list_display = ("owner", "created_at", "last_used_at", "is_active")
2019
+ list_display = ("assistant_name", "owner", "created_at", "last_used_at", "is_active")
1998
2020
  readonly_fields = ("user_key_hash", "created_at", "last_used_at")
1999
2021
 
2000
2022
  change_form_template = "admin/workgroupassistantprofile_change_form.html"
@@ -2006,7 +2028,15 @@ class AssistantProfileAdmin(
2006
2028
  ("Credentials", {"fields": ("user_key_hash",)}),
2007
2029
  (
2008
2030
  "Configuration",
2009
- {"fields": ("scopes", "is_active", "created_at", "last_used_at")},
2031
+ {
2032
+ "fields": (
2033
+ "assistant_name",
2034
+ "scopes",
2035
+ "is_active",
2036
+ "created_at",
2037
+ "last_used_at",
2038
+ )
2039
+ },
2010
2040
  ),
2011
2041
  )
2012
2042
 
@@ -2810,8 +2840,10 @@ class RFIDResource(resources.ModelResource):
2810
2840
  "energy_accounts",
2811
2841
  "reference",
2812
2842
  "external_command",
2843
+ "post_auth_command",
2813
2844
  "allowed",
2814
2845
  "color",
2846
+ "endianness",
2815
2847
  "kind",
2816
2848
  "released",
2817
2849
  "last_seen_on",
@@ -2823,8 +2855,10 @@ class RFIDResource(resources.ModelResource):
2823
2855
  "energy_accounts",
2824
2856
  "reference",
2825
2857
  "external_command",
2858
+ "post_auth_command",
2826
2859
  "allowed",
2827
2860
  "color",
2861
+ "endianness",
2828
2862
  "kind",
2829
2863
  "released",
2830
2864
  "last_seen_on",
@@ -2895,19 +2929,23 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
2895
2929
  "user_data_flag",
2896
2930
  "color",
2897
2931
  "kind",
2932
+ "endianness",
2898
2933
  "released",
2899
2934
  "allowed",
2900
2935
  "last_seen_on",
2901
2936
  )
2902
- list_filter = ("color", "released", "allowed")
2937
+ list_filter = ("color", "endianness", "released", "allowed")
2903
2938
  search_fields = ("label_id", "rfid", "custom_label")
2904
2939
  autocomplete_fields = ["energy_accounts"]
2905
2940
  raw_id_fields = ["reference"]
2906
2941
  actions = [
2907
2942
  "scan_rfids",
2908
2943
  "print_card_labels",
2944
+ "print_release_form",
2909
2945
  "copy_rfids",
2910
2946
  "toggle_selected_user_data",
2947
+ "toggle_selected_released",
2948
+ "toggle_selected_allowed",
2911
2949
  ]
2912
2950
  readonly_fields = ("added_on", "last_seen_on")
2913
2951
  form = RFIDForm
@@ -3005,6 +3043,50 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
3005
3043
  level=messages.WARNING,
3006
3044
  )
3007
3045
 
3046
+ @admin.action(description=_("Toggle Released flag"))
3047
+ def toggle_selected_released(self, request, queryset):
3048
+ manager = getattr(self.model, "all_objects", self.model.objects)
3049
+ toggled = 0
3050
+ for tag in queryset:
3051
+ new_state = not tag.released
3052
+ manager.filter(pk=tag.pk).update(released=new_state)
3053
+ tag.released = new_state
3054
+ toggled += 1
3055
+
3056
+ if toggled:
3057
+ self.message_user(
3058
+ request,
3059
+ ngettext(
3060
+ "Toggled released flag for %(count)d RFID.",
3061
+ "Toggled released flag for %(count)d RFIDs.",
3062
+ toggled,
3063
+ )
3064
+ % {"count": toggled},
3065
+ level=messages.SUCCESS,
3066
+ )
3067
+
3068
+ @admin.action(description=_("Toggle Allowed flag"))
3069
+ def toggle_selected_allowed(self, request, queryset):
3070
+ manager = getattr(self.model, "all_objects", self.model.objects)
3071
+ toggled = 0
3072
+ for tag in queryset:
3073
+ new_state = not tag.allowed
3074
+ manager.filter(pk=tag.pk).update(allowed=new_state)
3075
+ tag.allowed = new_state
3076
+ toggled += 1
3077
+
3078
+ if toggled:
3079
+ self.message_user(
3080
+ request,
3081
+ ngettext(
3082
+ "Toggled allowed flag for %(count)d RFID.",
3083
+ "Toggled allowed flag for %(count)d RFIDs.",
3084
+ toggled,
3085
+ )
3086
+ % {"count": toggled},
3087
+ level=messages.SUCCESS,
3088
+ )
3089
+
3008
3090
  @admin.action(description=_("Copy RFID"))
3009
3091
  def copy_rfids(self, request, queryset):
3010
3092
  if queryset.count() != 1:
@@ -3047,6 +3129,7 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
3047
3129
  "key_b_verified": source.key_b_verified,
3048
3130
  "allowed": source.allowed,
3049
3131
  "external_command": source.external_command,
3132
+ "post_auth_command": source.post_auth_command,
3050
3133
  "color": source.color,
3051
3134
  "kind": source.kind,
3052
3135
  "reference": source.reference,
@@ -3262,6 +3345,141 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
3262
3345
 
3263
3346
  print_card_labels.short_description = _("Print Card Labels")
3264
3347
 
3348
+ def _render_release_form(self, request, queryset, empty_message, redirect_url):
3349
+ tags = list(queryset)
3350
+ if not tags:
3351
+ self.message_user(request, empty_message, level=messages.WARNING)
3352
+ return HttpResponseRedirect(redirect_url)
3353
+
3354
+ language = getattr(request, "LANGUAGE_CODE", translation.get_language())
3355
+ if not language:
3356
+ language = settings.LANGUAGE_CODE
3357
+
3358
+ with translation.override(language):
3359
+ buffer = BytesIO()
3360
+ document = SimpleDocTemplate(
3361
+ buffer,
3362
+ pagesize=letter,
3363
+ leftMargin=36,
3364
+ rightMargin=36,
3365
+ topMargin=72,
3366
+ bottomMargin=36,
3367
+ )
3368
+ document.title = str(_("RFID Release Form"))
3369
+
3370
+ styles = getSampleStyleSheet()
3371
+ story = []
3372
+ story.append(Paragraph(_("RFID Release Form"), styles["Title"]))
3373
+ story.append(Spacer(1, 12))
3374
+
3375
+ generated_on = timezone.localtime()
3376
+ formatted_generated_on = date_format(generated_on, "DATETIME_FORMAT")
3377
+ if generated_on.tzinfo:
3378
+ formatted_generated_on = _("%(datetime)s %(timezone)s") % {
3379
+ "datetime": formatted_generated_on,
3380
+ "timezone": generated_on.tzname() or "",
3381
+ }
3382
+ generated_text = Paragraph(
3383
+ _("Generated on: %(date)s")
3384
+ % {"date": formatted_generated_on},
3385
+ styles["Normal"],
3386
+ )
3387
+ story.append(generated_text)
3388
+ story.append(Spacer(1, 24))
3389
+
3390
+ table_data = [
3391
+ [
3392
+ _("Label"),
3393
+ _("RFID"),
3394
+ _("Custom label"),
3395
+ _("Color"),
3396
+ _("Type"),
3397
+ ]
3398
+ ]
3399
+
3400
+ for tag in tags:
3401
+ table_data.append(
3402
+ [
3403
+ tag.label_id or "",
3404
+ tag.rfid or "",
3405
+ tag.custom_label or "",
3406
+ tag.get_color_display() if tag.color else "",
3407
+ tag.get_kind_display() if tag.kind else "",
3408
+ ]
3409
+ )
3410
+
3411
+ table = Table(table_data, repeatRows=1, hAlign="LEFT")
3412
+ table.setStyle(
3413
+ TableStyle(
3414
+ [
3415
+ ("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
3416
+ ("TEXTCOLOR", (0, 0), (-1, 0), colors.black),
3417
+ ("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
3418
+ ("ALIGN", (0, 0), (-1, -1), "LEFT"),
3419
+ ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
3420
+ ("BOTTOMPADDING", (0, 0), (-1, 0), 8),
3421
+ ]
3422
+ )
3423
+ )
3424
+
3425
+ story.append(table)
3426
+ story.append(Spacer(1, 36))
3427
+
3428
+ signature_lines = [
3429
+ [
3430
+ Paragraph(
3431
+ _("Issuer Signature: ______________________________"),
3432
+ styles["Normal"],
3433
+ ),
3434
+ Paragraph(
3435
+ _("Receiver Signature: ______________________________"),
3436
+ styles["Normal"],
3437
+ ),
3438
+ ],
3439
+ [
3440
+ Paragraph(
3441
+ _("Issuer Name: ______________________________"),
3442
+ styles["Normal"],
3443
+ ),
3444
+ Paragraph(
3445
+ _("Receiver Name: ______________________________"),
3446
+ styles["Normal"],
3447
+ ),
3448
+ ],
3449
+ ]
3450
+
3451
+ signature_table = Table(
3452
+ signature_lines,
3453
+ colWidths=[document.width / 2.0, document.width / 2.0],
3454
+ hAlign="LEFT",
3455
+ )
3456
+ signature_table.setStyle(
3457
+ TableStyle(
3458
+ [
3459
+ ("VALIGN", (0, 0), (-1, -1), "TOP"),
3460
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 12),
3461
+ ]
3462
+ )
3463
+ )
3464
+ story.append(signature_table)
3465
+
3466
+ document.build(story)
3467
+ buffer.seek(0)
3468
+
3469
+ response = HttpResponse(buffer.getvalue(), content_type="application/pdf")
3470
+ response["Content-Disposition"] = "attachment; filename=rfid-release-form.pdf"
3471
+ return response
3472
+
3473
+ def print_release_form(self, request, queryset):
3474
+ return self._render_release_form(
3475
+ request,
3476
+ queryset,
3477
+ _("Select at least one RFID to print the release form."),
3478
+ request.get_full_path(),
3479
+ )
3480
+
3481
+ print_release_form.short_description = _("Print Release Form")
3482
+
3265
3483
  def get_changelist_actions(self, request):
3266
3484
  parent = getattr(super(), "get_changelist_actions", None)
3267
3485
  actions = []
@@ -3336,6 +3554,7 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
3336
3554
  context["title"] = _("Scan RFIDs")
3337
3555
  context["opts"] = self.model._meta
3338
3556
  context["show_release_info"] = True
3557
+ context["default_endianness"] = RFID.BIG_ENDIAN
3339
3558
  return render(request, "admin/core/rfid/scan.html", context)
3340
3559
 
3341
3560
  def scan_next(self, request):
@@ -3349,9 +3568,11 @@ class RFIDAdmin(EntityModelAdmin, ImportExportModelAdmin):
3349
3568
  return JsonResponse({"error": "Invalid JSON payload"}, status=400)
3350
3569
  rfid = payload.get("rfid") or payload.get("value")
3351
3570
  kind = payload.get("kind")
3352
- result = validate_rfid_value(rfid, kind=kind)
3571
+ endianness = payload.get("endianness")
3572
+ result = validate_rfid_value(rfid, kind=kind, endianness=endianness)
3353
3573
  else:
3354
- result = scan_sources(request)
3574
+ endianness = request.GET.get("endianness")
3575
+ result = scan_sources(request, endianness=endianness)
3355
3576
  status = 500 if result.get("error") else 200
3356
3577
  return JsonResponse(result, status=status)
3357
3578
 
@@ -3542,7 +3763,7 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
3542
3763
  list_display_links = ("version",)
3543
3764
  actions = ["publish_release", "validate_releases", "test_pypi_connection"]
3544
3765
  change_actions = ["publish_release_action", "test_pypi_connection_action"]
3545
- changelist_actions = ["refresh_from_pypi", "prepare_next_release"]
3766
+ changelist_actions = ["edit_changelog", "refresh_from_pypi", "prepare_next_release"]
3546
3767
  readonly_fields = ("pypi_url", "github_url", "release_on", "is_current", "revision")
3547
3768
  fields = (
3548
3769
  "package",
@@ -3565,6 +3786,12 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, EntityModelAdmin):
3565
3786
 
3566
3787
  revision_short.short_description = "revision"
3567
3788
 
3789
+ def edit_changelog(self, request, queryset=None):
3790
+ return redirect("admin:system-changelog-report")
3791
+
3792
+ edit_changelog.label = "Edit Changelog"
3793
+ edit_changelog.short_description = "Edit Changelog"
3794
+
3568
3795
  def refresh_from_pypi(self, request, queryset):
3569
3796
  package = Package.objects.filter(is_active=True).first()
3570
3797
  if not package:
@@ -9,7 +9,11 @@ from django.contrib.admindocs.views import (
9
9
  BaseAdminDocsView,
10
10
  user_has_model_view_permission,
11
11
  )
12
+ from django.shortcuts import render
13
+ from django.template import loader
12
14
  from django.urls import NoReverseMatch, reverse
15
+ from django.utils.translation import gettext_lazy as _
16
+ from django.test import signals as test_signals
13
17
 
14
18
 
15
19
  class CommandsView(BaseAdminDocsView):
@@ -56,17 +60,27 @@ class CommandsView(BaseAdminDocsView):
56
60
  class OrderedModelIndexView(BaseAdminDocsView):
57
61
  template_name = "admin_doc/model_index.html"
58
62
 
63
+ USER_MANUALS_APP = SimpleNamespace(
64
+ label="manuals",
65
+ name="manuals",
66
+ verbose_name=_("User Manuals"),
67
+ )
68
+
59
69
  GROUP_OVERRIDES = {
60
70
  "ocpp.location": "core",
61
71
  "core.rfid": "ocpp",
62
72
  "core.package": "teams",
63
73
  "core.packagerelease": "teams",
74
+ "core.todo": "teams",
75
+ "pages.usermanual": USER_MANUALS_APP,
64
76
  }
65
77
 
66
78
  def _get_docs_app_config(self, meta):
67
- override_label = self.GROUP_OVERRIDES.get(meta.label_lower)
68
- if override_label:
69
- return apps.get_app_config(override_label)
79
+ override = self.GROUP_OVERRIDES.get(meta.label_lower)
80
+ if override:
81
+ if isinstance(override, str):
82
+ return apps.get_app_config(override)
83
+ return override
70
84
  return meta.app_config
71
85
 
72
86
  def get_context_data(self, **kwargs):
@@ -92,6 +106,33 @@ class OrderedModelIndexView(BaseAdminDocsView):
92
106
  class ModelGraphIndexView(BaseAdminDocsView):
93
107
  template_name = "admin_doc/model_graphs.html"
94
108
 
109
+ def render_to_response(self, context, **response_kwargs):
110
+ template_name = response_kwargs.pop("template_name", None)
111
+ if template_name is None:
112
+ template_name = self.get_template_names()
113
+ response = render(
114
+ self.request,
115
+ template_name,
116
+ context,
117
+ **response_kwargs,
118
+ )
119
+ if getattr(response, "context", None) is None:
120
+ response.context = context
121
+ if test_signals.template_rendered.receivers:
122
+ if isinstance(template_name, (list, tuple)):
123
+ template = loader.select_template(template_name)
124
+ else:
125
+ template = loader.get_template(template_name)
126
+ signal_context = context
127
+ if self.request is not None and "request" not in signal_context:
128
+ signal_context = {**context, "request": self.request}
129
+ test_signals.template_rendered.send(
130
+ sender=template.__class__,
131
+ template=template,
132
+ context=signal_context,
133
+ )
134
+ return response
135
+
95
136
  def get_context_data(self, **kwargs):
96
137
  sections = {}
97
138
  user = self.request.user
@@ -351,6 +351,6 @@ class CoreConfig(AppConfig):
351
351
  try:
352
352
  from .mcp.auto_start import schedule_auto_start
353
353
 
354
- schedule_auto_start()
354
+ schedule_auto_start(check_profiles_immediately=False)
355
355
  except Exception: # pragma: no cover - defensive
356
356
  logger.exception("Failed to schedule MCP auto-start")