arthexis 0.1.7__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.
- {arthexis-0.1.7 → arthexis-0.1.8}/PKG-INFO +13 -9
- arthexis-0.1.8/README.md +22 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/arthexis.egg-info/PKG-INFO +13 -9
- {arthexis-0.1.7 → arthexis-0.1.8}/arthexis.egg-info/SOURCES.txt +10 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/settings.py +1 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/urls.py +9 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/admin.py +137 -6
- arthexis-0.1.8/core/admindocs.py +44 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/models.py +75 -5
- {arthexis-0.1.7 → arthexis-0.1.8}/core/tests.py +94 -2
- arthexis-0.1.8/core/workgroup_urls.py +13 -0
- arthexis-0.1.8/core/workgroup_views.py +57 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/apps.py +3 -2
- {arthexis-0.1.7 → arthexis-0.1.8}/pyproject.toml +2 -2
- arthexis-0.1.8/tests/test_admin_doc_commands.py +32 -0
- arthexis-0.1.8/tests/test_business_admin_group.py +27 -0
- arthexis-0.1.8/tests/test_chat_profile_admin.py +38 -0
- arthexis-0.1.8/tests/test_chat_profile_api.py +31 -0
- arthexis-0.1.8/tests/test_experience_admin_group.py +27 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_reference_qr_code.py +1 -1
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_rfid_background_reader.py +15 -5
- arthexis-0.1.8/tests/test_show_leads_command.py +63 -0
- arthexis-0.1.8/tests/test_update_fixtures_command.py +28 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_workgroup_admin_group.py +16 -1
- arthexis-0.1.7/README.md +0 -18
- {arthexis-0.1.7 → arthexis-0.1.8}/LICENSE +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/arthexis.egg-info/dependency_links.txt +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/arthexis.egg-info/requires.txt +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/arthexis.egg-info/top_level.txt +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/__init__.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/active_app.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/asgi.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/auth_app.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/celery.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/context_processors.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/horologia_app.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/loadenv.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/logging.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/middleware.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/offline.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/workgroup_app.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/config/wsgi.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/__init__.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/apps.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/backends.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/checks.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/entity.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/environment.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/fields.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/lcd_screen.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/middleware.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/notifications.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/release.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/system.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/tasks.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/test_system_info.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/urls.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/user_data.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/core/views.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/__init__.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/actions.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/admin.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/apps.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/lcd.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/models.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/tasks.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/tests.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/urls.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/utils.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/nodes/views.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/__init__.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/admin.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/consumers.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/evcs.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/models.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/routing.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/simulator.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/store.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/tasks.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/test_export_import.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/test_rfid.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/tests.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/transactions_io.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/urls.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/ocpp/views.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/__init__.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/admin.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/apps.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/checks.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/context_processors.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/models.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/tests.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/urls.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/utils.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/pages/views.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/setup.cfg +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_acronym_capitalization.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_admin_history.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_admin_index_actions.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_awg_admin.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_celery_no_debug.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_csrf_failure.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_csrf_origin_subnet.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_dist_cleanup.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_email_collector.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_email_inbox.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_email_inbox_admin.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_email_inbox_search_action.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_env_refresh_clean.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_env_refresh_unlink.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_fixture_check.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_footer_no_references.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_footer_presence.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_footer_render.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_install_script.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_language_switch.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_lcd_smbus2.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_localhost_admin_backend.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_model_verbose_name_capitalization.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_notifications_fallback.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_notify_command.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_odoo_profile.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_odoo_profile_admin.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_offline.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_pypi_token.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_readme_language.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_reference_transaction_uuid.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_register_site_apps_command.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_release_logs.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_release_progress.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_release_tasks.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_rfid_admin_reference_clear.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_rfid_admin_scan_csrf.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_save_as_copy.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_seed_data.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_sigil_resolution.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_urls_autodiscover.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_user_datum_admin.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_version_file.py +0 -0
- {arthexis-0.1.7 → arthexis-0.1.8}/tests/test_vscode_manage.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arthexis
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Django-based MESH system
|
|
5
|
-
Author-email: "Rafael J.
|
|
5
|
+
Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Repository, https://github.com/arthexis/arthexis
|
|
8
8
|
Project-URL: Homepage, https://arthexis.com
|
|
@@ -109,18 +109,22 @@ Dynamic: license-file
|
|
|
109
109
|
# Arthexis Constellation
|
|
110
110
|
|
|
111
111
|
## Purpose
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
arthexis-0.1.8/README.md
ADDED
|
@@ -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,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arthexis
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Django-based MESH system
|
|
5
|
-
Author-email: "Rafael J.
|
|
5
|
+
Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Repository, https://github.com/arthexis/arthexis
|
|
8
8
|
Project-URL: Homepage, https://arthexis.com
|
|
@@ -109,18 +109,22 @@ Dynamic: license-file
|
|
|
109
109
|
# Arthexis Constellation
|
|
110
110
|
|
|
111
111
|
## Purpose
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -23,6 +23,7 @@ config/workgroup_app.py
|
|
|
23
23
|
config/wsgi.py
|
|
24
24
|
core/__init__.py
|
|
25
25
|
core/admin.py
|
|
26
|
+
core/admindocs.py
|
|
26
27
|
core/apps.py
|
|
27
28
|
core/backends.py
|
|
28
29
|
core/checks.py
|
|
@@ -41,6 +42,8 @@ core/tests.py
|
|
|
41
42
|
core/urls.py
|
|
42
43
|
core/user_data.py
|
|
43
44
|
core/views.py
|
|
45
|
+
core/workgroup_urls.py
|
|
46
|
+
core/workgroup_views.py
|
|
44
47
|
nodes/__init__.py
|
|
45
48
|
nodes/actions.py
|
|
46
49
|
nodes/admin.py
|
|
@@ -79,10 +82,14 @@ pages/urls.py
|
|
|
79
82
|
pages/utils.py
|
|
80
83
|
pages/views.py
|
|
81
84
|
tests/test_acronym_capitalization.py
|
|
85
|
+
tests/test_admin_doc_commands.py
|
|
82
86
|
tests/test_admin_history.py
|
|
83
87
|
tests/test_admin_index_actions.py
|
|
84
88
|
tests/test_awg_admin.py
|
|
89
|
+
tests/test_business_admin_group.py
|
|
85
90
|
tests/test_celery_no_debug.py
|
|
91
|
+
tests/test_chat_profile_admin.py
|
|
92
|
+
tests/test_chat_profile_api.py
|
|
86
93
|
tests/test_csrf_failure.py
|
|
87
94
|
tests/test_csrf_origin_subnet.py
|
|
88
95
|
tests/test_dist_cleanup.py
|
|
@@ -92,6 +99,7 @@ tests/test_email_inbox_admin.py
|
|
|
92
99
|
tests/test_email_inbox_search_action.py
|
|
93
100
|
tests/test_env_refresh_clean.py
|
|
94
101
|
tests/test_env_refresh_unlink.py
|
|
102
|
+
tests/test_experience_admin_group.py
|
|
95
103
|
tests/test_fixture_check.py
|
|
96
104
|
tests/test_footer_no_references.py
|
|
97
105
|
tests/test_footer_presence.py
|
|
@@ -119,7 +127,9 @@ tests/test_rfid_admin_scan_csrf.py
|
|
|
119
127
|
tests/test_rfid_background_reader.py
|
|
120
128
|
tests/test_save_as_copy.py
|
|
121
129
|
tests/test_seed_data.py
|
|
130
|
+
tests/test_show_leads_command.py
|
|
122
131
|
tests/test_sigil_resolution.py
|
|
132
|
+
tests/test_update_fixtures_command.py
|
|
123
133
|
tests/test_urls_autodiscover.py
|
|
124
134
|
tests/test_user_datum_admin.py
|
|
125
135
|
tests/test_version_file.py
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -45,6 +45,7 @@ from .models import (
|
|
|
45
45
|
ReleaseManager,
|
|
46
46
|
SecurityGroup,
|
|
47
47
|
InviteLead,
|
|
48
|
+
ChatProfile,
|
|
48
49
|
)
|
|
49
50
|
from .user_data import UserDatumAdminMixin
|
|
50
51
|
|
|
@@ -52,6 +53,30 @@ from .user_data import UserDatumAdminMixin
|
|
|
52
53
|
admin.site.unregister(Group)
|
|
53
54
|
|
|
54
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
|
+
|
|
55
80
|
class SaveBeforeChangeAction(DjangoObjectActions):
|
|
56
81
|
def response_change(self, request, obj):
|
|
57
82
|
action = request.POST.get("_action")
|
|
@@ -65,7 +90,7 @@ class SaveBeforeChangeAction(DjangoObjectActions):
|
|
|
65
90
|
return super().response_change(request, obj)
|
|
66
91
|
|
|
67
92
|
|
|
68
|
-
@admin.register(
|
|
93
|
+
@admin.register(ExperienceReference)
|
|
69
94
|
class ReferenceAdmin(admin.ModelAdmin):
|
|
70
95
|
list_display = (
|
|
71
96
|
"alt_text",
|
|
@@ -141,14 +166,20 @@ class ReferenceAdmin(admin.ModelAdmin):
|
|
|
141
166
|
qr_code.short_description = "QR Code"
|
|
142
167
|
|
|
143
168
|
|
|
144
|
-
@admin.register(
|
|
169
|
+
@admin.register(WorkgroupReleaseManager)
|
|
145
170
|
class ReleaseManagerAdmin(admin.ModelAdmin):
|
|
146
171
|
list_display = ("user", "pypi_username", "pypi_url")
|
|
147
172
|
|
|
148
173
|
|
|
149
174
|
@admin.register(Package)
|
|
150
175
|
class PackageAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
|
|
151
|
-
list_display = (
|
|
176
|
+
list_display = (
|
|
177
|
+
"name",
|
|
178
|
+
"description",
|
|
179
|
+
"homepage_url",
|
|
180
|
+
"release_manager",
|
|
181
|
+
"is_active",
|
|
182
|
+
)
|
|
152
183
|
actions = ["prepare_next_release"]
|
|
153
184
|
change_actions = ["prepare_next_release_action"]
|
|
154
185
|
|
|
@@ -177,6 +208,24 @@ class PackageAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
|
|
|
177
208
|
reverse("admin:core_packagerelease_change", args=[release.pk])
|
|
178
209
|
)
|
|
179
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
|
+
|
|
180
229
|
@admin.action(description="Prepare next Release")
|
|
181
230
|
def prepare_next_release(self, request, queryset):
|
|
182
231
|
if queryset.count() != 1:
|
|
@@ -201,7 +250,7 @@ class SecurityGroupAdminForm(forms.ModelForm):
|
|
|
201
250
|
)
|
|
202
251
|
|
|
203
252
|
class Meta:
|
|
204
|
-
model =
|
|
253
|
+
model = WorkgroupSecurityGroup
|
|
205
254
|
fields = "__all__"
|
|
206
255
|
|
|
207
256
|
def __init__(self, *args, **kwargs):
|
|
@@ -219,7 +268,7 @@ class SecurityGroupAdminForm(forms.ModelForm):
|
|
|
219
268
|
return instance
|
|
220
269
|
|
|
221
270
|
|
|
222
|
-
@admin.register(
|
|
271
|
+
@admin.register(WorkgroupSecurityGroup)
|
|
223
272
|
class SecurityGroupAdmin(DjangoGroupAdmin):
|
|
224
273
|
form = SecurityGroupAdminForm
|
|
225
274
|
fieldsets = ((None, {"fields": ("name", "parent", "users", "permissions")}),)
|
|
@@ -517,6 +566,46 @@ class EmailInboxAdmin(admin.ModelAdmin):
|
|
|
517
566
|
return TemplateResponse(request, "admin/core/emailinbox/search.html", context)
|
|
518
567
|
|
|
519
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
|
+
|
|
520
609
|
class EnergyCreditInline(admin.TabularInline):
|
|
521
610
|
model = EnergyCredit
|
|
522
611
|
fields = ("amount_kw", "created_by", "created_on")
|
|
@@ -720,6 +809,7 @@ class RFIDForm(forms.ModelForm):
|
|
|
720
809
|
super().__init__(*args, **kwargs)
|
|
721
810
|
self.fields["reference"].required = False
|
|
722
811
|
rel = RFID._meta.get_field("reference").remote_field
|
|
812
|
+
rel.model = ExperienceReference
|
|
723
813
|
widget = self.fields["reference"].widget
|
|
724
814
|
self.fields["reference"].widget = RelatedFieldWidgetWrapper(
|
|
725
815
|
widget,
|
|
@@ -809,6 +899,7 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
|
|
|
809
899
|
list_display_links = ("version",)
|
|
810
900
|
actions = ["publish_release", "validate_releases"]
|
|
811
901
|
change_actions = ["publish_release_action"]
|
|
902
|
+
changelist_actions = ["refresh_from_pypi"]
|
|
812
903
|
readonly_fields = ("pypi_url", "is_current", "revision")
|
|
813
904
|
fields = (
|
|
814
905
|
"package",
|
|
@@ -829,6 +920,46 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
|
|
|
829
920
|
|
|
830
921
|
revision_short.short_description = "revision"
|
|
831
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
|
+
|
|
832
963
|
def _publish_release(self, request, release):
|
|
833
964
|
try:
|
|
834
965
|
release.full_clean()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from django.core.management import get_commands, load_command_class
|
|
3
|
+
from django.contrib.admindocs.views import BaseAdminDocsView
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CommandsView(BaseAdminDocsView):
|
|
7
|
+
template_name = "admin_doc/commands.html"
|
|
8
|
+
|
|
9
|
+
def get_context_data(self, **kwargs):
|
|
10
|
+
commands = []
|
|
11
|
+
for name, app_name in sorted(get_commands().items()):
|
|
12
|
+
try:
|
|
13
|
+
cmd = load_command_class(app_name, name)
|
|
14
|
+
parser = cmd.create_parser("manage.py", name)
|
|
15
|
+
except Exception: # pragma: no cover - command import issues
|
|
16
|
+
continue
|
|
17
|
+
args = []
|
|
18
|
+
options = []
|
|
19
|
+
for action in parser._actions:
|
|
20
|
+
if isinstance(action, argparse._HelpAction):
|
|
21
|
+
continue
|
|
22
|
+
if action.option_strings:
|
|
23
|
+
options.append(
|
|
24
|
+
{
|
|
25
|
+
"opts": ", ".join(action.option_strings),
|
|
26
|
+
"help": action.help or "",
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
else:
|
|
30
|
+
args.append(
|
|
31
|
+
{
|
|
32
|
+
"name": action.metavar or action.dest,
|
|
33
|
+
"help": action.help or "",
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
commands.append(
|
|
37
|
+
{
|
|
38
|
+
"name": name,
|
|
39
|
+
"help": getattr(cmd, "help", ""),
|
|
40
|
+
"args": args,
|
|
41
|
+
"options": options,
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
return super().get_context_data(**{**kwargs, "commands": commands})
|
|
@@ -17,6 +17,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
17
17
|
import hashlib
|
|
18
18
|
import os
|
|
19
19
|
import subprocess
|
|
20
|
+
import secrets
|
|
20
21
|
from io import BytesIO
|
|
21
22
|
from django.core.files.base import ContentFile
|
|
22
23
|
import qrcode
|
|
@@ -1153,14 +1154,30 @@ class Package(Entity):
|
|
|
1153
1154
|
release_manager = models.ForeignKey(
|
|
1154
1155
|
ReleaseManager, on_delete=models.SET_NULL, null=True, blank=True
|
|
1155
1156
|
)
|
|
1157
|
+
is_active = models.BooleanField(
|
|
1158
|
+
default=False,
|
|
1159
|
+
help_text="Designates the active package for version comparisons",
|
|
1160
|
+
)
|
|
1156
1161
|
|
|
1157
1162
|
class Meta:
|
|
1158
1163
|
verbose_name = "Package"
|
|
1159
1164
|
verbose_name_plural = "Packages"
|
|
1165
|
+
constraints = [
|
|
1166
|
+
models.UniqueConstraint(
|
|
1167
|
+
fields=("is_active",),
|
|
1168
|
+
condition=models.Q(is_active=True),
|
|
1169
|
+
name="unique_active_package",
|
|
1170
|
+
)
|
|
1171
|
+
]
|
|
1160
1172
|
|
|
1161
1173
|
def __str__(self) -> str: # pragma: no cover - trivial
|
|
1162
1174
|
return self.name
|
|
1163
1175
|
|
|
1176
|
+
def save(self, *args, **kwargs):
|
|
1177
|
+
if self.is_active:
|
|
1178
|
+
type(self).objects.exclude(pk=self.pk).update(is_active=False)
|
|
1179
|
+
super().save(*args, **kwargs)
|
|
1180
|
+
|
|
1164
1181
|
def to_package(self) -> ReleasePackage:
|
|
1165
1182
|
"""Return a :class:`ReleasePackage` instance from package data."""
|
|
1166
1183
|
return ReleasePackage(
|
|
@@ -1250,11 +1267,13 @@ class PackageRelease(Entity):
|
|
|
1250
1267
|
|
|
1251
1268
|
@property
|
|
1252
1269
|
def is_current(self) -> bool:
|
|
1253
|
-
"""Return ``True``
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1270
|
+
"""Return ``True`` when this release's version matches the VERSION file
|
|
1271
|
+
and its package is active."""
|
|
1272
|
+
version_path = Path("VERSION")
|
|
1273
|
+
if not version_path.exists():
|
|
1274
|
+
return False
|
|
1275
|
+
current_version = version_path.read_text().strip()
|
|
1276
|
+
return current_version == self.version and self.package.is_active
|
|
1258
1277
|
|
|
1259
1278
|
@classmethod
|
|
1260
1279
|
def latest(cls):
|
|
@@ -1316,3 +1335,54 @@ def _rfid_unique_energy_account(sender, instance, action, reverse, model, pk_set
|
|
|
1316
1335
|
).exclude(energy_accounts=instance)
|
|
1317
1336
|
if conflict.exists():
|
|
1318
1337
|
raise ValidationError("RFID tags may only be assigned to one energy account.")
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
def hash_key(key: str) -> str:
|
|
1341
|
+
"""Return a SHA-256 hash for ``key``."""
|
|
1342
|
+
|
|
1343
|
+
return hashlib.sha256(key.encode()).hexdigest()
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
class ChatProfile(models.Model):
|
|
1347
|
+
"""Stores a hashed user key used by the assistant for authentication.
|
|
1348
|
+
|
|
1349
|
+
The plain-text ``user_key`` is generated server-side and shown only once.
|
|
1350
|
+
Users must supply this key in the ``Authorization: Bearer <user_key>``
|
|
1351
|
+
header when requesting protected endpoints. Only the hash is stored.
|
|
1352
|
+
"""
|
|
1353
|
+
|
|
1354
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
1355
|
+
user = models.OneToOneField(
|
|
1356
|
+
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="chat_profile"
|
|
1357
|
+
)
|
|
1358
|
+
user_key_hash = models.CharField(max_length=64, unique=True)
|
|
1359
|
+
scopes = models.JSONField(default=list, blank=True)
|
|
1360
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
1361
|
+
last_used_at = models.DateTimeField(null=True, blank=True)
|
|
1362
|
+
is_active = models.BooleanField(default=True)
|
|
1363
|
+
|
|
1364
|
+
class Meta:
|
|
1365
|
+
db_table = "workgroup_chatprofile"
|
|
1366
|
+
verbose_name = "Chat Profile"
|
|
1367
|
+
verbose_name_plural = "Chat Profiles"
|
|
1368
|
+
|
|
1369
|
+
@classmethod
|
|
1370
|
+
def issue_key(cls, user) -> tuple["ChatProfile", str]:
|
|
1371
|
+
"""Create or update a profile and return it with a new plain key."""
|
|
1372
|
+
|
|
1373
|
+
key = secrets.token_hex(32)
|
|
1374
|
+
key_hash = hash_key(key)
|
|
1375
|
+
profile, _ = cls.objects.update_or_create(
|
|
1376
|
+
user=user,
|
|
1377
|
+
defaults={"user_key_hash": key_hash, "last_used_at": None, "is_active": True},
|
|
1378
|
+
)
|
|
1379
|
+
return profile, key
|
|
1380
|
+
|
|
1381
|
+
def touch(self) -> None:
|
|
1382
|
+
"""Record that the key was used."""
|
|
1383
|
+
|
|
1384
|
+
self.last_used_at = timezone.now()
|
|
1385
|
+
self.save(update_fields=["last_used_at"])
|
|
1386
|
+
|
|
1387
|
+
def __str__(self) -> str: # pragma: no cover - simple representation
|
|
1388
|
+
return f"ChatProfile for {self.user}"
|