arthexis 0.1.6__tar.gz → 0.1.8__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 (147) hide show
  1. {arthexis-0.1.6 → arthexis-0.1.8}/PKG-INFO +12 -8
  2. arthexis-0.1.8/README.md +22 -0
  3. {arthexis-0.1.6 → arthexis-0.1.8}/arthexis.egg-info/PKG-INFO +12 -8
  4. {arthexis-0.1.6 → arthexis-0.1.8}/arthexis.egg-info/SOURCES.txt +20 -7
  5. {arthexis-0.1.6 → arthexis-0.1.8}/config/celery.py +7 -0
  6. arthexis-0.1.8/config/horologia_app.py +7 -0
  7. {arthexis-0.1.6 → arthexis-0.1.8}/config/logging.py +8 -3
  8. {arthexis-0.1.6 → arthexis-0.1.8}/config/settings.py +3 -2
  9. {arthexis-0.1.6 → arthexis-0.1.8}/config/urls.py +9 -0
  10. arthexis-0.1.8/config/workgroup_app.py +7 -0
  11. {arthexis-0.1.6 → arthexis-0.1.8}/core/admin.py +192 -17
  12. arthexis-0.1.8/core/admindocs.py +44 -0
  13. {arthexis-0.1.6 → arthexis-0.1.8}/core/apps.py +2 -1
  14. arthexis-0.1.8/core/checks.py +29 -0
  15. {arthexis-0.1.6 → arthexis-0.1.8}/core/entity.py +29 -7
  16. {arthexis-0.1.6 → arthexis-0.1.8}/core/models.py +124 -14
  17. {arthexis-0.1.6 → arthexis-0.1.8}/core/release.py +29 -141
  18. {arthexis-0.1.6 → arthexis-0.1.8}/core/system.py +2 -2
  19. arthexis-0.1.8/core/test_system_info.py +21 -0
  20. {arthexis-0.1.6 → arthexis-0.1.8}/core/tests.py +292 -1
  21. {arthexis-0.1.6 → arthexis-0.1.8}/core/views.py +153 -134
  22. arthexis-0.1.8/core/workgroup_urls.py +13 -0
  23. arthexis-0.1.8/core/workgroup_views.py +57 -0
  24. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/admin.py +211 -0
  25. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/apps.py +1 -1
  26. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/models.py +103 -7
  27. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/tests.py +27 -0
  28. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/apps.py +4 -3
  29. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/models.py +1 -1
  30. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/simulator.py +4 -0
  31. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/tests.py +5 -1
  32. {arthexis-0.1.6 → arthexis-0.1.8}/pages/admin.py +8 -3
  33. {arthexis-0.1.6 → arthexis-0.1.8}/pages/apps.py +1 -1
  34. {arthexis-0.1.6 → arthexis-0.1.8}/pages/tests.py +23 -4
  35. {arthexis-0.1.6 → arthexis-0.1.8}/pages/views.py +22 -3
  36. {arthexis-0.1.6 → arthexis-0.1.8}/pyproject.toml +1 -1
  37. arthexis-0.1.8/tests/test_admin_doc_commands.py +32 -0
  38. arthexis-0.1.8/tests/test_business_admin_group.py +27 -0
  39. arthexis-0.1.8/tests/test_celery_no_debug.py +19 -0
  40. arthexis-0.1.8/tests/test_chat_profile_admin.py +38 -0
  41. arthexis-0.1.8/tests/test_chat_profile_api.py +31 -0
  42. arthexis-0.1.8/tests/test_dist_cleanup.py +32 -0
  43. arthexis-0.1.8/tests/test_env_refresh_clean.py +19 -0
  44. arthexis-0.1.8/tests/test_experience_admin_group.py +27 -0
  45. arthexis-0.1.8/tests/test_fixture_check.py +62 -0
  46. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_install_script.py +21 -1
  47. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_reference_qr_code.py +1 -1
  48. arthexis-0.1.8/tests/test_release_progress.py +80 -0
  49. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_rfid_background_reader.py +15 -5
  50. arthexis-0.1.8/tests/test_show_leads_command.py +63 -0
  51. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_sigil_resolution.py +28 -0
  52. arthexis-0.1.8/tests/test_update_fixtures_command.py +28 -0
  53. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_user_datum_admin.py +0 -1
  54. arthexis-0.1.8/tests/test_version_file.py +23 -0
  55. arthexis-0.1.6/tests/test_post_office_admin_group.py → arthexis-0.1.8/tests/test_workgroup_admin_group.py +18 -3
  56. arthexis-0.1.6/README.md +0 -18
  57. arthexis-0.1.6/tests/test_env_refresh_clean.py +0 -30
  58. arthexis-0.1.6/tests/test_footer_admin_link.py +0 -91
  59. arthexis-0.1.6/tests/test_github_token.py +0 -29
  60. arthexis-0.1.6/tests/test_package_release_admin_actions.py +0 -199
  61. arthexis-0.1.6/tests/test_release_fixture_cleanup.py +0 -37
  62. arthexis-0.1.6/tests/test_release_mapping.py +0 -24
  63. arthexis-0.1.6/tests/test_release_progress.py +0 -165
  64. {arthexis-0.1.6 → arthexis-0.1.8}/LICENSE +0 -0
  65. {arthexis-0.1.6 → arthexis-0.1.8}/arthexis.egg-info/dependency_links.txt +0 -0
  66. {arthexis-0.1.6 → arthexis-0.1.8}/arthexis.egg-info/requires.txt +0 -0
  67. {arthexis-0.1.6 → arthexis-0.1.8}/arthexis.egg-info/top_level.txt +0 -0
  68. {arthexis-0.1.6 → arthexis-0.1.8}/config/__init__.py +0 -0
  69. {arthexis-0.1.6 → arthexis-0.1.8}/config/active_app.py +0 -0
  70. {arthexis-0.1.6 → arthexis-0.1.8}/config/asgi.py +0 -0
  71. {arthexis-0.1.6 → arthexis-0.1.8}/config/auth_app.py +0 -0
  72. {arthexis-0.1.6 → arthexis-0.1.8}/config/context_processors.py +0 -0
  73. {arthexis-0.1.6 → arthexis-0.1.8}/config/loadenv.py +0 -0
  74. {arthexis-0.1.6 → arthexis-0.1.8}/config/middleware.py +0 -0
  75. {arthexis-0.1.6 → arthexis-0.1.8}/config/offline.py +0 -0
  76. {arthexis-0.1.6 → arthexis-0.1.8}/config/wsgi.py +0 -0
  77. {arthexis-0.1.6 → arthexis-0.1.8}/core/__init__.py +0 -0
  78. {arthexis-0.1.6 → arthexis-0.1.8}/core/backends.py +0 -0
  79. {arthexis-0.1.6 → arthexis-0.1.8}/core/environment.py +0 -0
  80. {arthexis-0.1.6 → arthexis-0.1.8}/core/fields.py +0 -0
  81. {arthexis-0.1.6 → arthexis-0.1.8}/core/lcd_screen.py +0 -0
  82. {arthexis-0.1.6 → arthexis-0.1.8}/core/middleware.py +0 -0
  83. {arthexis-0.1.6 → arthexis-0.1.8}/core/notifications.py +0 -0
  84. {arthexis-0.1.6 → arthexis-0.1.8}/core/tasks.py +0 -0
  85. {arthexis-0.1.6 → arthexis-0.1.8}/core/urls.py +0 -0
  86. {arthexis-0.1.6 → arthexis-0.1.8}/core/user_data.py +0 -0
  87. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/__init__.py +0 -0
  88. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/actions.py +0 -0
  89. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/lcd.py +0 -0
  90. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/tasks.py +0 -0
  91. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/urls.py +0 -0
  92. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/utils.py +0 -0
  93. {arthexis-0.1.6 → arthexis-0.1.8}/nodes/views.py +0 -0
  94. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/__init__.py +0 -0
  95. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/admin.py +0 -0
  96. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/consumers.py +0 -0
  97. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/evcs.py +0 -0
  98. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/routing.py +0 -0
  99. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/store.py +0 -0
  100. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/tasks.py +0 -0
  101. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/test_export_import.py +0 -0
  102. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/test_rfid.py +0 -0
  103. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/transactions_io.py +0 -0
  104. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/urls.py +0 -0
  105. {arthexis-0.1.6 → arthexis-0.1.8}/ocpp/views.py +0 -0
  106. {arthexis-0.1.6 → arthexis-0.1.8}/pages/__init__.py +0 -0
  107. {arthexis-0.1.6 → arthexis-0.1.8}/pages/checks.py +0 -0
  108. {arthexis-0.1.6 → arthexis-0.1.8}/pages/context_processors.py +0 -0
  109. {arthexis-0.1.6 → arthexis-0.1.8}/pages/models.py +0 -0
  110. {arthexis-0.1.6 → arthexis-0.1.8}/pages/urls.py +0 -0
  111. {arthexis-0.1.6 → arthexis-0.1.8}/pages/utils.py +0 -0
  112. {arthexis-0.1.6 → arthexis-0.1.8}/setup.cfg +0 -0
  113. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_acronym_capitalization.py +0 -0
  114. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_admin_history.py +0 -0
  115. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_admin_index_actions.py +0 -0
  116. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_awg_admin.py +0 -0
  117. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_csrf_failure.py +0 -0
  118. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_csrf_origin_subnet.py +0 -0
  119. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_email_collector.py +0 -0
  120. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_email_inbox.py +0 -0
  121. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_email_inbox_admin.py +0 -0
  122. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_email_inbox_search_action.py +0 -0
  123. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_env_refresh_unlink.py +0 -0
  124. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_footer_no_references.py +0 -0
  125. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_footer_presence.py +0 -0
  126. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_footer_render.py +0 -0
  127. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_language_switch.py +0 -0
  128. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_lcd_smbus2.py +0 -0
  129. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_localhost_admin_backend.py +0 -0
  130. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_model_verbose_name_capitalization.py +0 -0
  131. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_notifications_fallback.py +0 -0
  132. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_notify_command.py +0 -0
  133. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_odoo_profile.py +0 -0
  134. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_odoo_profile_admin.py +0 -0
  135. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_offline.py +0 -0
  136. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_pypi_token.py +0 -0
  137. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_readme_language.py +0 -0
  138. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_reference_transaction_uuid.py +0 -0
  139. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_register_site_apps_command.py +0 -0
  140. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_release_logs.py +0 -0
  141. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_release_tasks.py +0 -0
  142. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_rfid_admin_reference_clear.py +0 -0
  143. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_rfid_admin_scan_csrf.py +0 -0
  144. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_save_as_copy.py +0 -0
  145. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_seed_data.py +0 -0
  146. {arthexis-0.1.6 → arthexis-0.1.8}/tests/test_urls_autodiscover.py +0 -0
  147. {arthexis-0.1.6 → arthexis-0.1.8}/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.6
3
+ Version: 0.1.8
4
4
  Summary: Django-based MESH system
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: MIT
@@ -109,18 +109,22 @@ Dynamic: license-file
109
109
  # Arthexis Constellation
110
110
 
111
111
  ## Purpose
112
- Arthexis Constellation is a narrative-driven Django-based suite that centralizes tools for managing charging infrastructure and orchestrating energy related products and services.
112
+
113
+ Arthexis Constellation is a [narrative-driven](https://en.wikipedia.org/wiki/Narrative) [Django](https://www.djangoproject.com/)-based [software suite](https://en.wikipedia.org/wiki/Software_suite) that centralizes tools for managing [electric vehicle charging infrastructure](https://en.wikipedia.org/wiki/Charging_station) and orchestrating [energy](https://en.wikipedia.org/wiki/Energy)-related [products](https://en.wikipedia.org/wiki/Product_(business)) and [services](https://en.wikipedia.org/wiki/Service_(economics)).
113
114
 
114
115
  ## Features
115
- - Compatibility with all OCPP 1.6 certified chargers
116
- - Native API integration with Odoo 1.6-based CRMs
117
- - Runs everywhere Python 3.10+ is installed
118
- - Single codebase with special features per role
116
+
117
+ - Compatible with the [Open Charge Point Protocol (OCPP) 1.6](https://www.openchargealliance.org/protocols/ocpp-16/)
118
+ - [API](https://en.wikipedia.org/wiki/API) integration with [Odoo](https://www.odoo.com/) 1.6
119
+ - Runs on [Windows 11](https://www.microsoft.com/windows/windows-11) and [Ubuntu 22.04 LTS](https://releases.ubuntu.com/22.04/)
120
+ - Tested for the [Raspberry Pi 4 Model B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/)
119
121
 
120
122
  ## Support
121
- You may contact us at tecnologia at gelectriic dot com or visit our [web page](https://www.gelectriic.com/) for professional services and commercial support.
123
+
124
+ Contact us at [tecnologia@gelectriic.com](mailto:tecnologia@gelectriic.com) or visit our [web page](https://www.gelectriic.com/) for [professional services](https://en.wikipedia.org/wiki/Professional_services) and [commercial support](https://en.wikipedia.org/wiki/Technical_support).
122
125
 
123
126
  ## About Me
124
- > "What? You want to know about me too? Well, I enjoy developing software, roleplaying games, long walks on the beach and a fourth secret thing."
127
+
128
+ > "What, you want to know about me too? Well, I enjoy [developing software](https://en.wikipedia.org/wiki/Software_development), [role-playing games](https://en.wikipedia.org/wiki/Role-playing_game), long walks on the [beach](https://en.wikipedia.org/wiki/Beach) and a fourth secret thing."
125
129
  > --Arthexis
126
130
 
@@ -0,0 +1,22 @@
1
+ # Arthexis Constellation
2
+
3
+ ## Purpose
4
+
5
+ Arthexis Constellation is a [narrative-driven](https://en.wikipedia.org/wiki/Narrative) [Django](https://www.djangoproject.com/)-based [software suite](https://en.wikipedia.org/wiki/Software_suite) that centralizes tools for managing [electric vehicle charging infrastructure](https://en.wikipedia.org/wiki/Charging_station) and orchestrating [energy](https://en.wikipedia.org/wiki/Energy)-related [products](https://en.wikipedia.org/wiki/Product_(business)) and [services](https://en.wikipedia.org/wiki/Service_(economics)).
6
+
7
+ ## Features
8
+
9
+ - Compatible with the [Open Charge Point Protocol (OCPP) 1.6](https://www.openchargealliance.org/protocols/ocpp-16/)
10
+ - [API](https://en.wikipedia.org/wiki/API) integration with [Odoo](https://www.odoo.com/) 1.6
11
+ - Runs on [Windows 11](https://www.microsoft.com/windows/windows-11) and [Ubuntu 22.04 LTS](https://releases.ubuntu.com/22.04/)
12
+ - Tested for the [Raspberry Pi 4 Model B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/)
13
+
14
+ ## Support
15
+
16
+ Contact us at [tecnologia@gelectriic.com](mailto:tecnologia@gelectriic.com) or visit our [web page](https://www.gelectriic.com/) for [professional services](https://en.wikipedia.org/wiki/Professional_services) and [commercial support](https://en.wikipedia.org/wiki/Technical_support).
17
+
18
+ ## About Me
19
+
20
+ > "What, you want to know about me too? Well, I enjoy [developing software](https://en.wikipedia.org/wiki/Software_development), [role-playing games](https://en.wikipedia.org/wiki/Role-playing_game), long walks on the [beach](https://en.wikipedia.org/wiki/Beach) and a fourth secret thing."
21
+ > --Arthexis
22
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: Django-based MESH system
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: MIT
@@ -109,18 +109,22 @@ Dynamic: license-file
109
109
  # Arthexis Constellation
110
110
 
111
111
  ## Purpose
112
- Arthexis Constellation is a narrative-driven Django-based suite that centralizes tools for managing charging infrastructure and orchestrating energy related products and services.
112
+
113
+ Arthexis Constellation is a [narrative-driven](https://en.wikipedia.org/wiki/Narrative) [Django](https://www.djangoproject.com/)-based [software suite](https://en.wikipedia.org/wiki/Software_suite) that centralizes tools for managing [electric vehicle charging infrastructure](https://en.wikipedia.org/wiki/Charging_station) and orchestrating [energy](https://en.wikipedia.org/wiki/Energy)-related [products](https://en.wikipedia.org/wiki/Product_(business)) and [services](https://en.wikipedia.org/wiki/Service_(economics)).
113
114
 
114
115
  ## Features
115
- - Compatibility with all OCPP 1.6 certified chargers
116
- - Native API integration with Odoo 1.6-based CRMs
117
- - Runs everywhere Python 3.10+ is installed
118
- - Single codebase with special features per role
116
+
117
+ - Compatible with the [Open Charge Point Protocol (OCPP) 1.6](https://www.openchargealliance.org/protocols/ocpp-16/)
118
+ - [API](https://en.wikipedia.org/wiki/API) integration with [Odoo](https://www.odoo.com/) 1.6
119
+ - Runs on [Windows 11](https://www.microsoft.com/windows/windows-11) and [Ubuntu 22.04 LTS](https://releases.ubuntu.com/22.04/)
120
+ - Tested for the [Raspberry Pi 4 Model B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/)
119
121
 
120
122
  ## Support
121
- You may contact us at tecnologia at gelectriic dot com or visit our [web page](https://www.gelectriic.com/) for professional services and commercial support.
123
+
124
+ Contact us at [tecnologia@gelectriic.com](mailto:tecnologia@gelectriic.com) or visit our [web page](https://www.gelectriic.com/) for [professional services](https://en.wikipedia.org/wiki/Professional_services) and [commercial support](https://en.wikipedia.org/wiki/Technical_support).
122
125
 
123
126
  ## About Me
124
- > "What? You want to know about me too? Well, I enjoy developing software, roleplaying games, long walks on the beach and a fourth secret thing."
127
+
128
+ > "What, you want to know about me too? Well, I enjoy [developing software](https://en.wikipedia.org/wiki/Software_development), [role-playing games](https://en.wikipedia.org/wiki/Role-playing_game), long walks on the [beach](https://en.wikipedia.org/wiki/Beach) and a fourth secret thing."
125
129
  > --Arthexis
126
130
 
@@ -12,17 +12,21 @@ config/asgi.py
12
12
  config/auth_app.py
13
13
  config/celery.py
14
14
  config/context_processors.py
15
+ config/horologia_app.py
15
16
  config/loadenv.py
16
17
  config/logging.py
17
18
  config/middleware.py
18
19
  config/offline.py
19
20
  config/settings.py
20
21
  config/urls.py
22
+ config/workgroup_app.py
21
23
  config/wsgi.py
22
24
  core/__init__.py
23
25
  core/admin.py
26
+ core/admindocs.py
24
27
  core/apps.py
25
28
  core/backends.py
29
+ core/checks.py
26
30
  core/entity.py
27
31
  core/environment.py
28
32
  core/fields.py
@@ -33,10 +37,13 @@ core/notifications.py
33
37
  core/release.py
34
38
  core/system.py
35
39
  core/tasks.py
40
+ core/test_system_info.py
36
41
  core/tests.py
37
42
  core/urls.py
38
43
  core/user_data.py
39
44
  core/views.py
45
+ core/workgroup_urls.py
46
+ core/workgroup_views.py
40
47
  nodes/__init__.py
41
48
  nodes/actions.py
42
49
  nodes/admin.py
@@ -75,22 +82,28 @@ pages/urls.py
75
82
  pages/utils.py
76
83
  pages/views.py
77
84
  tests/test_acronym_capitalization.py
85
+ tests/test_admin_doc_commands.py
78
86
  tests/test_admin_history.py
79
87
  tests/test_admin_index_actions.py
80
88
  tests/test_awg_admin.py
89
+ tests/test_business_admin_group.py
90
+ tests/test_celery_no_debug.py
91
+ tests/test_chat_profile_admin.py
92
+ tests/test_chat_profile_api.py
81
93
  tests/test_csrf_failure.py
82
94
  tests/test_csrf_origin_subnet.py
95
+ tests/test_dist_cleanup.py
83
96
  tests/test_email_collector.py
84
97
  tests/test_email_inbox.py
85
98
  tests/test_email_inbox_admin.py
86
99
  tests/test_email_inbox_search_action.py
87
100
  tests/test_env_refresh_clean.py
88
101
  tests/test_env_refresh_unlink.py
89
- tests/test_footer_admin_link.py
102
+ tests/test_experience_admin_group.py
103
+ tests/test_fixture_check.py
90
104
  tests/test_footer_no_references.py
91
105
  tests/test_footer_presence.py
92
106
  tests/test_footer_render.py
93
- tests/test_github_token.py
94
107
  tests/test_install_script.py
95
108
  tests/test_language_switch.py
96
109
  tests/test_lcd_smbus2.py
@@ -101,16 +114,12 @@ tests/test_notify_command.py
101
114
  tests/test_odoo_profile.py
102
115
  tests/test_odoo_profile_admin.py
103
116
  tests/test_offline.py
104
- tests/test_package_release_admin_actions.py
105
- tests/test_post_office_admin_group.py
106
117
  tests/test_pypi_token.py
107
118
  tests/test_readme_language.py
108
119
  tests/test_reference_qr_code.py
109
120
  tests/test_reference_transaction_uuid.py
110
121
  tests/test_register_site_apps_command.py
111
- tests/test_release_fixture_cleanup.py
112
122
  tests/test_release_logs.py
113
- tests/test_release_mapping.py
114
123
  tests/test_release_progress.py
115
124
  tests/test_release_tasks.py
116
125
  tests/test_rfid_admin_reference_clear.py
@@ -118,7 +127,11 @@ tests/test_rfid_admin_scan_csrf.py
118
127
  tests/test_rfid_background_reader.py
119
128
  tests/test_save_as_copy.py
120
129
  tests/test_seed_data.py
130
+ tests/test_show_leads_command.py
121
131
  tests/test_sigil_resolution.py
132
+ tests/test_update_fixtures_command.py
122
133
  tests/test_urls_autodiscover.py
123
134
  tests/test_user_datum_admin.py
124
- tests/test_vscode_manage.py
135
+ tests/test_version_file.py
136
+ tests/test_vscode_manage.py
137
+ tests/test_workgroup_admin_group.py
@@ -7,6 +7,13 @@ from celery import Celery
7
7
 
8
8
  os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
9
9
 
10
+ # When running on production-oriented nodes, avoid Celery debug mode.
11
+ NODE_ROLE = os.environ.get("NODE_ROLE", "")
12
+ if NODE_ROLE in {"Constellation", "Satellite", "Virtual"}:
13
+ for var in ["CELERY_TRACE_APP", "CELERY_DEBUG"]:
14
+ os.environ.pop(var, None)
15
+ os.environ.setdefault("CELERY_LOG_LEVEL", "INFO")
16
+
10
17
  app = Celery("config")
11
18
  app.config_from_object("django.conf:settings", namespace="CELERY")
12
19
  app.autodiscover_tasks()
@@ -0,0 +1,7 @@
1
+ from django_celery_beat.apps import BeatConfig as BaseBeatConfig
2
+
3
+
4
+ class HorologiaConfig(BaseBeatConfig):
5
+ """Customize Periodic Tasks app label."""
6
+
7
+ verbose_name = "5. Horologia"
@@ -13,14 +13,17 @@ class ActiveAppFileHandler(TimedRotatingFileHandler):
13
13
  """File handler that writes to a file named after the active app."""
14
14
 
15
15
  def _current_file(self) -> Path:
16
+ log_dir = Path(settings.LOG_DIR)
17
+ log_dir.mkdir(parents=True, exist_ok=True)
16
18
  if "test" in sys.argv:
17
- return Path(settings.LOG_DIR) / "tests.log"
18
- return Path(settings.LOG_DIR) / f"{get_active_app()}.log"
19
+ return log_dir / "tests.log"
20
+ return log_dir / f"{get_active_app()}.log"
19
21
 
20
22
  def emit(self, record: logging.LogRecord) -> None:
21
23
  current = str(self._current_file())
22
24
  if self.baseFilename != current:
23
25
  self.baseFilename = current
26
+ Path(self.baseFilename).parent.mkdir(parents=True, exist_ok=True)
24
27
  if self.stream:
25
28
  self.stream.close()
26
29
  self.stream = self._open()
@@ -29,7 +32,9 @@ class ActiveAppFileHandler(TimedRotatingFileHandler):
29
32
  def rotation_filename(self, default_name: str) -> str:
30
33
  """Place rotated logs inside the old log directory."""
31
34
  default_path = Path(default_name)
32
- return str(Path(settings.OLD_LOG_DIR) / default_path.name)
35
+ old_log_dir = Path(settings.OLD_LOG_DIR)
36
+ old_log_dir.mkdir(parents=True, exist_ok=True)
37
+ return str(old_log_dir / default_path.name)
33
38
 
34
39
  def getFilesToDelete(self):
35
40
  """Return files to delete in the old log directory respecting backupCount."""
@@ -85,6 +85,7 @@ ALLOWED_HOSTS = [
85
85
  "10.42.0.0/16",
86
86
  "192.168.0.0/16",
87
87
  "arthexis.com",
88
+ "www.arthexis.com",
88
89
  ]
89
90
 
90
91
 
@@ -148,8 +149,8 @@ INSTALLED_APPS = [
148
149
  "django_object_actions",
149
150
  "django.contrib.sites",
150
151
  "channels",
151
- "post_office",
152
- "django_celery_beat",
152
+ "config.workgroup_app.WorkgroupConfig",
153
+ "config.horologia_app.HorologiaConfig",
153
154
  ] + LOCAL_APPS
154
155
 
155
156
  if DEBUG:
@@ -13,12 +13,15 @@ from django.apps import apps
13
13
  from django.conf import settings
14
14
  from django.conf.urls.static import static
15
15
  from django.contrib import admin
16
+ from django.contrib.admin import autodiscover
16
17
  from django.urls import include, path
17
18
  from django.views.decorators.csrf import csrf_exempt
18
19
  from django.views.i18n import set_language
19
20
  from django.utils.translation import gettext_lazy as _
20
21
  from core import views as core_views
22
+ from core.admindocs import CommandsView
21
23
 
24
+ autodiscover()
22
25
  admin.site.site_header = _("Constellation")
23
26
  admin.site.site_title = _("Constellation")
24
27
 
@@ -61,6 +64,11 @@ def autodiscovered_urlpatterns():
61
64
 
62
65
 
63
66
  urlpatterns = [
67
+ path(
68
+ "admin/doc/commands/",
69
+ CommandsView.as_view(),
70
+ name="django-admindocs-commands",
71
+ ),
64
72
  path("admin/doc/", include("django.contrib.admindocs.urls")),
65
73
  path(
66
74
  "admin/core/releases/<int:pk>/<str:action>/",
@@ -69,6 +77,7 @@ urlpatterns = [
69
77
  ),
70
78
  path("admin/", admin.site.urls),
71
79
  path("i18n/setlang/", csrf_exempt(set_language), name="set_language"),
80
+ path("api/", include("core.workgroup_urls")),
72
81
  path("", include("pages.urls")),
73
82
  ]
74
83
 
@@ -0,0 +1,7 @@
1
+ from post_office.apps import PostOfficeConfig as BasePostOfficeConfig
2
+
3
+
4
+ class WorkgroupConfig(BasePostOfficeConfig):
5
+ """Customize Post Office app label."""
6
+
7
+ verbose_name = "6. Workgroup"
@@ -3,7 +3,7 @@ from django.contrib import admin
3
3
  from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
4
4
  from django.urls import path, reverse
5
5
  from django.shortcuts import redirect, render
6
- from django.http import JsonResponse, HttpResponseBase
6
+ from django.http import JsonResponse, HttpResponseBase, HttpResponseRedirect
7
7
  from django.template.response import TemplateResponse
8
8
  from django.views.decorators.csrf import csrf_exempt
9
9
  from django.core.exceptions import ValidationError
@@ -21,6 +21,7 @@ from django.templatetags.static import static
21
21
  from django.utils.html import format_html
22
22
  import json
23
23
  import uuid
24
+ import requests
24
25
  from django_object_actions import DjangoObjectActions
25
26
  from .user_data import UserDatumAdminMixin
26
27
  from .models import (
@@ -43,6 +44,8 @@ from .models import (
43
44
  PackageRelease,
44
45
  ReleaseManager,
45
46
  SecurityGroup,
47
+ InviteLead,
48
+ ChatProfile,
46
49
  )
47
50
  from .user_data import UserDatumAdminMixin
48
51
 
@@ -50,6 +53,30 @@ from .user_data import UserDatumAdminMixin
50
53
  admin.site.unregister(Group)
51
54
 
52
55
 
56
+ class WorkgroupReleaseManager(ReleaseManager):
57
+ class Meta:
58
+ proxy = True
59
+ app_label = "post_office"
60
+ verbose_name = ReleaseManager._meta.verbose_name
61
+ verbose_name_plural = ReleaseManager._meta.verbose_name_plural
62
+
63
+
64
+ class WorkgroupSecurityGroup(SecurityGroup):
65
+ class Meta:
66
+ proxy = True
67
+ app_label = "post_office"
68
+ verbose_name = SecurityGroup._meta.verbose_name
69
+ verbose_name_plural = SecurityGroup._meta.verbose_name_plural
70
+
71
+
72
+ class ExperienceReference(Reference):
73
+ class Meta:
74
+ proxy = True
75
+ app_label = "pages"
76
+ verbose_name = Reference._meta.verbose_name
77
+ verbose_name_plural = Reference._meta.verbose_name_plural
78
+
79
+
53
80
  class SaveBeforeChangeAction(DjangoObjectActions):
54
81
  def response_change(self, request, obj):
55
82
  action = request.POST.get("_action")
@@ -63,7 +90,7 @@ class SaveBeforeChangeAction(DjangoObjectActions):
63
90
  return super().response_change(request, obj)
64
91
 
65
92
 
66
- @admin.register(Reference)
93
+ @admin.register(ExperienceReference)
67
94
  class ReferenceAdmin(admin.ModelAdmin):
68
95
  list_display = (
69
96
  "alt_text",
@@ -139,14 +166,20 @@ class ReferenceAdmin(admin.ModelAdmin):
139
166
  qr_code.short_description = "QR Code"
140
167
 
141
168
 
142
- @admin.register(ReleaseManager)
169
+ @admin.register(WorkgroupReleaseManager)
143
170
  class ReleaseManagerAdmin(admin.ModelAdmin):
144
171
  list_display = ("user", "pypi_username", "pypi_url")
145
172
 
146
173
 
147
174
  @admin.register(Package)
148
175
  class PackageAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
149
- list_display = ("name", "description", "homepage_url", "release_manager")
176
+ list_display = (
177
+ "name",
178
+ "description",
179
+ "homepage_url",
180
+ "release_manager",
181
+ "is_active",
182
+ )
150
183
  actions = ["prepare_next_release"]
151
184
  change_actions = ["prepare_next_release_action"]
152
185
 
@@ -175,6 +208,24 @@ class PackageAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
175
208
  reverse("admin:core_packagerelease_change", args=[release.pk])
176
209
  )
177
210
 
211
+ def get_urls(self):
212
+ urls = super().get_urls()
213
+ custom = [
214
+ path(
215
+ "prepare-next-release/",
216
+ self.admin_site.admin_view(self.prepare_next_release_active),
217
+ name="core_package_prepare_next_release",
218
+ )
219
+ ]
220
+ return custom + urls
221
+
222
+ def prepare_next_release_active(self, request):
223
+ package = Package.objects.filter(is_active=True).first()
224
+ if not package:
225
+ self.message_user(request, "No active package", messages.ERROR)
226
+ return redirect("admin:core_package_changelist")
227
+ return self._prepare(request, package)
228
+
178
229
  @admin.action(description="Prepare next Release")
179
230
  def prepare_next_release(self, request, queryset):
180
231
  if queryset.count() != 1:
@@ -199,7 +250,7 @@ class SecurityGroupAdminForm(forms.ModelForm):
199
250
  )
200
251
 
201
252
  class Meta:
202
- model = SecurityGroup
253
+ model = WorkgroupSecurityGroup
203
254
  fields = "__all__"
204
255
 
205
256
  def __init__(self, *args, **kwargs):
@@ -217,13 +268,27 @@ class SecurityGroupAdminForm(forms.ModelForm):
217
268
  return instance
218
269
 
219
270
 
220
- @admin.register(SecurityGroup)
271
+ @admin.register(WorkgroupSecurityGroup)
221
272
  class SecurityGroupAdmin(DjangoGroupAdmin):
222
273
  form = SecurityGroupAdminForm
223
274
  fieldsets = ((None, {"fields": ("name", "parent", "users", "permissions")}),)
224
275
  filter_horizontal = ("permissions",)
225
276
 
226
277
 
278
+ @admin.register(InviteLead)
279
+ class InviteLeadAdmin(admin.ModelAdmin):
280
+ list_display = ("email", "created_on")
281
+ search_fields = ("email", "comment")
282
+ readonly_fields = (
283
+ "created_on",
284
+ "user",
285
+ "path",
286
+ "referer",
287
+ "user_agent",
288
+ "ip_address",
289
+ )
290
+
291
+
227
292
  class EnergyAccountRFIDForm(forms.ModelForm):
228
293
  """Form for assigning existing RFIDs to an energy account."""
229
294
 
@@ -501,6 +566,46 @@ class EmailInboxAdmin(admin.ModelAdmin):
501
566
  return TemplateResponse(request, "admin/core/emailinbox/search.html", context)
502
567
 
503
568
 
569
+ class WorkgroupChatProfile(ChatProfile):
570
+ class Meta:
571
+ proxy = True
572
+ app_label = "post_office"
573
+ verbose_name = ChatProfile._meta.verbose_name
574
+ verbose_name_plural = ChatProfile._meta.verbose_name_plural
575
+
576
+
577
+ @admin.register(WorkgroupChatProfile)
578
+ class ChatProfileAdmin(admin.ModelAdmin):
579
+ list_display = ("user", "created_at", "last_used_at", "is_active")
580
+ readonly_fields = ("user_key_hash",)
581
+
582
+ change_form_template = "admin/workgroupchatprofile_change_form.html"
583
+
584
+ def get_urls(self):
585
+ urls = super().get_urls()
586
+ custom = [
587
+ path(
588
+ "<path:object_id>/generate-key/",
589
+ self.admin_site.admin_view(self.generate_key),
590
+ name="post_office_workgroupchatprofile_generate_key",
591
+ ),
592
+ ]
593
+ return custom + urls
594
+
595
+ def generate_key(self, request, object_id, *args, **kwargs):
596
+ profile = self.get_object(request, object_id)
597
+ if profile is None:
598
+ return HttpResponseRedirect("../")
599
+ profile, key = ChatProfile.issue_key(profile.user)
600
+ context = {
601
+ **self.admin_site.each_context(request),
602
+ "opts": self.model._meta,
603
+ "original": profile,
604
+ "user_key": key,
605
+ }
606
+ return TemplateResponse(request, "admin/chatprofile_key.html", context)
607
+
608
+
504
609
  class EnergyCreditInline(admin.TabularInline):
505
610
  model = EnergyCredit
506
611
  fields = ("amount_kw", "created_by", "created_on")
@@ -704,6 +809,7 @@ class RFIDForm(forms.ModelForm):
704
809
  super().__init__(*args, **kwargs)
705
810
  self.fields["reference"].required = False
706
811
  rel = RFID._meta.get_field("reference").remote_field
812
+ rel.model = ExperienceReference
707
813
  widget = self.fields["reference"].widget
708
814
  self.fields["reference"].widget = RelatedFieldWidgetWrapper(
709
815
  widget,
@@ -784,17 +890,17 @@ class RFIDAdmin(ImportExportModelAdmin):
784
890
  class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
785
891
  list_display = (
786
892
  "version",
787
- "package",
893
+ "package_link",
788
894
  "is_current",
789
895
  "pypi_url",
790
- "pr_link",
791
896
  "revision_short",
792
897
  "published_status",
793
898
  )
794
899
  list_display_links = ("version",)
795
- actions = ["publish_release"]
900
+ actions = ["publish_release", "validate_releases"]
796
901
  change_actions = ["publish_release_action"]
797
- readonly_fields = ("pypi_url", "pr_url", "is_current", "revision")
902
+ changelist_actions = ["refresh_from_pypi"]
903
+ readonly_fields = ("pypi_url", "is_current", "revision")
798
904
  fields = (
799
905
  "package",
800
906
  "release_manager",
@@ -802,14 +908,58 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
802
908
  "revision",
803
909
  "is_current",
804
910
  "pypi_url",
805
- "pr_url",
806
911
  )
807
912
 
913
+ @admin.display(description="package", ordering="package")
914
+ def package_link(self, obj):
915
+ url = reverse("admin:core_package_change", args=[obj.package_id])
916
+ return format_html('<a href="{}">{}</a>', url, obj.package)
917
+
808
918
  def revision_short(self, obj):
809
919
  return obj.revision_short
810
920
 
811
921
  revision_short.short_description = "revision"
812
922
 
923
+ def refresh_from_pypi(self, request, queryset):
924
+ package = Package.objects.filter(is_active=True).first()
925
+ if not package:
926
+ self.message_user(request, "No active package", messages.ERROR)
927
+ return
928
+ try:
929
+ resp = requests.get(
930
+ f"https://pypi.org/pypi/{package.name}/json", timeout=10
931
+ )
932
+ resp.raise_for_status()
933
+ except Exception as exc: # pragma: no cover - network failure
934
+ self.message_user(request, str(exc), messages.ERROR)
935
+ return
936
+ releases = resp.json().get("releases", {})
937
+ created = 0
938
+ for version in releases:
939
+ exists = PackageRelease.all_objects.filter(
940
+ package=package, version=version
941
+ ).exists()
942
+ if not exists:
943
+ PackageRelease.objects.create(
944
+ package=package,
945
+ release_manager=package.release_manager,
946
+ version=version,
947
+ pypi_url=f"https://pypi.org/project/{package.name}/{version}/",
948
+ )
949
+ created += 1
950
+ if created:
951
+ PackageRelease.dump_fixture()
952
+ self.message_user(
953
+ request,
954
+ f"Created {created} release{'s' if created != 1 else ''} from PyPI",
955
+ messages.SUCCESS,
956
+ )
957
+ else:
958
+ self.message_user(request, "No new releases found", messages.INFO)
959
+
960
+ refresh_from_pypi.label = "Refresh from PyPI"
961
+ refresh_from_pypi.short_description = "Refresh from PyPI"
962
+
813
963
  def _publish_release(self, request, release):
814
964
  try:
815
965
  release.full_clean()
@@ -833,6 +983,37 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
833
983
  publish_release_action.label = "Publish selected Release"
834
984
  publish_release_action.short_description = "Publish this release"
835
985
 
986
+ @admin.action(description="Validate selected Releases")
987
+ def validate_releases(self, request, queryset):
988
+ deleted = False
989
+ for release in queryset:
990
+ if not release.pypi_url:
991
+ self.message_user(
992
+ request,
993
+ f"{release} has not been published yet",
994
+ messages.WARNING,
995
+ )
996
+ continue
997
+ url = (
998
+ f"https://pypi.org/pypi/{release.package.name}/{release.version}/json"
999
+ )
1000
+ try:
1001
+ resp = requests.get(url, timeout=10)
1002
+ except Exception as exc: # pragma: no cover - network failure
1003
+ self.message_user(request, f"{release}: {exc}", messages.ERROR)
1004
+ continue
1005
+ if resp.status_code == 200:
1006
+ continue
1007
+ release.delete()
1008
+ deleted = True
1009
+ self.message_user(
1010
+ request,
1011
+ f"Deleted {release} as it was not found on PyPI",
1012
+ messages.WARNING,
1013
+ )
1014
+ if deleted:
1015
+ PackageRelease.dump_fixture()
1016
+
836
1017
  @staticmethod
837
1018
  def _boolean_icon(value: bool) -> str:
838
1019
  icon = static("admin/img/icon-yes.svg" if value else "admin/img/icon-no.svg")
@@ -847,10 +1028,4 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
847
1028
  def is_current(self, obj):
848
1029
  return self._boolean_icon(obj.is_current)
849
1030
 
850
- def pr_link(self, obj):
851
- if obj.pr_url:
852
- return format_html('<a href="{0}" target="_blank">{0}</a>', obj.pr_url)
853
- return ""
854
-
855
- pr_link.short_description = "PR URL"
856
1031