arthexis 0.1.6__tar.gz → 0.1.7__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.6 → arthexis-0.1.7}/PKG-INFO +8 -8
- arthexis-0.1.7/README.md +18 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/arthexis.egg-info/PKG-INFO +8 -8
- {arthexis-0.1.6 → arthexis-0.1.7}/arthexis.egg-info/SOURCES.txt +10 -7
- {arthexis-0.1.6 → arthexis-0.1.7}/config/celery.py +7 -0
- arthexis-0.1.7/config/horologia_app.py +7 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/logging.py +8 -3
- {arthexis-0.1.6 → arthexis-0.1.7}/config/settings.py +2 -2
- arthexis-0.1.7/config/workgroup_app.py +7 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/admin.py +55 -11
- {arthexis-0.1.6 → arthexis-0.1.7}/core/apps.py +2 -1
- arthexis-0.1.7/core/checks.py +29 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/entity.py +29 -7
- {arthexis-0.1.6 → arthexis-0.1.7}/core/models.py +49 -9
- {arthexis-0.1.6 → arthexis-0.1.7}/core/release.py +29 -141
- {arthexis-0.1.6 → arthexis-0.1.7}/core/system.py +2 -2
- arthexis-0.1.7/core/test_system_info.py +21 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/tests.py +200 -1
- {arthexis-0.1.6 → arthexis-0.1.7}/core/views.py +153 -134
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/admin.py +211 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/apps.py +1 -1
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/models.py +103 -7
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/tests.py +27 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/apps.py +1 -1
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/models.py +1 -1
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/simulator.py +4 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/tests.py +5 -1
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/admin.py +8 -3
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/apps.py +1 -1
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/tests.py +23 -4
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/views.py +22 -3
- {arthexis-0.1.6 → arthexis-0.1.7}/pyproject.toml +2 -2
- arthexis-0.1.7/tests/test_celery_no_debug.py +19 -0
- arthexis-0.1.7/tests/test_dist_cleanup.py +32 -0
- arthexis-0.1.7/tests/test_env_refresh_clean.py +19 -0
- arthexis-0.1.7/tests/test_fixture_check.py +62 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_install_script.py +21 -1
- arthexis-0.1.7/tests/test_release_progress.py +80 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_sigil_resolution.py +28 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_user_datum_admin.py +0 -1
- arthexis-0.1.7/tests/test_version_file.py +23 -0
- arthexis-0.1.6/tests/test_post_office_admin_group.py → arthexis-0.1.7/tests/test_workgroup_admin_group.py +2 -2
- arthexis-0.1.6/README.md +0 -18
- arthexis-0.1.6/tests/test_env_refresh_clean.py +0 -30
- arthexis-0.1.6/tests/test_footer_admin_link.py +0 -91
- arthexis-0.1.6/tests/test_github_token.py +0 -29
- arthexis-0.1.6/tests/test_package_release_admin_actions.py +0 -199
- arthexis-0.1.6/tests/test_release_fixture_cleanup.py +0 -37
- arthexis-0.1.6/tests/test_release_mapping.py +0 -24
- arthexis-0.1.6/tests/test_release_progress.py +0 -165
- {arthexis-0.1.6 → arthexis-0.1.7}/LICENSE +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/arthexis.egg-info/dependency_links.txt +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/arthexis.egg-info/requires.txt +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/arthexis.egg-info/top_level.txt +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/__init__.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/active_app.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/asgi.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/auth_app.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/context_processors.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/loadenv.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/middleware.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/offline.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/urls.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/config/wsgi.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/__init__.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/backends.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/environment.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/fields.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/lcd_screen.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/middleware.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/notifications.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/tasks.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/urls.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/core/user_data.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/__init__.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/actions.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/lcd.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/tasks.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/urls.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/utils.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/nodes/views.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/__init__.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/admin.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/consumers.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/evcs.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/routing.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/store.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/tasks.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/test_export_import.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/test_rfid.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/transactions_io.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/urls.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/ocpp/views.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/__init__.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/checks.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/context_processors.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/models.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/urls.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/pages/utils.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/setup.cfg +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_acronym_capitalization.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_admin_history.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_admin_index_actions.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_awg_admin.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_csrf_failure.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_csrf_origin_subnet.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_email_collector.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_email_inbox.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_email_inbox_admin.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_email_inbox_search_action.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_env_refresh_unlink.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_footer_no_references.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_footer_presence.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_footer_render.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_language_switch.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_lcd_smbus2.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_localhost_admin_backend.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_model_verbose_name_capitalization.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_notifications_fallback.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_notify_command.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_odoo_profile.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_odoo_profile_admin.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_offline.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_pypi_token.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_readme_language.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_reference_qr_code.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_reference_transaction_uuid.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_register_site_apps_command.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_release_logs.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_release_tasks.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_rfid_admin_reference_clear.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_rfid_admin_scan_csrf.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_rfid_background_reader.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_save_as_copy.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_seed_data.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/tests/test_urls_autodiscover.py +0 -0
- {arthexis-0.1.6 → arthexis-0.1.7}/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.7
|
|
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
|
|
@@ -112,15 +112,15 @@ Dynamic: license-file
|
|
|
112
112
|
Arthexis Constellation is a narrative-driven Django-based suite that centralizes tools for managing charging infrastructure and orchestrating energy related products and services.
|
|
113
113
|
|
|
114
114
|
## Features
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
- Runs
|
|
118
|
-
-
|
|
115
|
+
- Compatible with OCPP 1.6+ chargers
|
|
116
|
+
- API integration with Odoo 1.6+
|
|
117
|
+
- Runs on Windows and Linux (Ubuntu 16+)
|
|
118
|
+
- One codebase. Six* specialized Roles.
|
|
119
119
|
|
|
120
120
|
## Support
|
|
121
|
-
|
|
121
|
+
Contact us at tecnologia at gelectriic dot com or visit our [web page](https://www.gelectriic.com/) for professional services and commercial support.
|
|
122
122
|
|
|
123
123
|
## About Me
|
|
124
|
-
> "What
|
|
124
|
+
> "What, you wanna know about me too? Well, I enjoy developing software, role-playing games, long walks on the beach and a fourth secret thing."
|
|
125
125
|
> --Arthexis
|
|
126
126
|
|
arthexis-0.1.7/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Arthexis Constellation
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Arthexis Constellation is a narrative-driven Django-based suite that centralizes tools for managing charging infrastructure and orchestrating energy related products and services.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
- Compatible with OCPP 1.6+ chargers
|
|
8
|
+
- API integration with Odoo 1.6+
|
|
9
|
+
- Runs on Windows and Linux (Ubuntu 16+)
|
|
10
|
+
- One codebase. Six* specialized Roles.
|
|
11
|
+
|
|
12
|
+
## Support
|
|
13
|
+
Contact us at tecnologia at gelectriic dot com or visit our [web page](https://www.gelectriic.com/) for professional services and commercial support.
|
|
14
|
+
|
|
15
|
+
## About Me
|
|
16
|
+
> "What, you wanna know about me too? Well, I enjoy developing software, role-playing games, long walks on the beach and a fourth secret thing."
|
|
17
|
+
> --Arthexis
|
|
18
|
+
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arthexis
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
@@ -112,15 +112,15 @@ Dynamic: license-file
|
|
|
112
112
|
Arthexis Constellation is a narrative-driven Django-based suite that centralizes tools for managing charging infrastructure and orchestrating energy related products and services.
|
|
113
113
|
|
|
114
114
|
## Features
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
- Runs
|
|
118
|
-
-
|
|
115
|
+
- Compatible with OCPP 1.6+ chargers
|
|
116
|
+
- API integration with Odoo 1.6+
|
|
117
|
+
- Runs on Windows and Linux (Ubuntu 16+)
|
|
118
|
+
- One codebase. Six* specialized Roles.
|
|
119
119
|
|
|
120
120
|
## Support
|
|
121
|
-
|
|
121
|
+
Contact us at tecnologia at gelectriic dot com or visit our [web page](https://www.gelectriic.com/) for professional services and commercial support.
|
|
122
122
|
|
|
123
123
|
## About Me
|
|
124
|
-
> "What
|
|
124
|
+
> "What, you wanna know about me too? Well, I enjoy developing software, role-playing games, long walks on the beach and a fourth secret thing."
|
|
125
125
|
> --Arthexis
|
|
126
126
|
|
|
@@ -12,17 +12,20 @@ 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
|
|
24
26
|
core/apps.py
|
|
25
27
|
core/backends.py
|
|
28
|
+
core/checks.py
|
|
26
29
|
core/entity.py
|
|
27
30
|
core/environment.py
|
|
28
31
|
core/fields.py
|
|
@@ -33,6 +36,7 @@ core/notifications.py
|
|
|
33
36
|
core/release.py
|
|
34
37
|
core/system.py
|
|
35
38
|
core/tasks.py
|
|
39
|
+
core/test_system_info.py
|
|
36
40
|
core/tests.py
|
|
37
41
|
core/urls.py
|
|
38
42
|
core/user_data.py
|
|
@@ -78,19 +82,20 @@ tests/test_acronym_capitalization.py
|
|
|
78
82
|
tests/test_admin_history.py
|
|
79
83
|
tests/test_admin_index_actions.py
|
|
80
84
|
tests/test_awg_admin.py
|
|
85
|
+
tests/test_celery_no_debug.py
|
|
81
86
|
tests/test_csrf_failure.py
|
|
82
87
|
tests/test_csrf_origin_subnet.py
|
|
88
|
+
tests/test_dist_cleanup.py
|
|
83
89
|
tests/test_email_collector.py
|
|
84
90
|
tests/test_email_inbox.py
|
|
85
91
|
tests/test_email_inbox_admin.py
|
|
86
92
|
tests/test_email_inbox_search_action.py
|
|
87
93
|
tests/test_env_refresh_clean.py
|
|
88
94
|
tests/test_env_refresh_unlink.py
|
|
89
|
-
tests/
|
|
95
|
+
tests/test_fixture_check.py
|
|
90
96
|
tests/test_footer_no_references.py
|
|
91
97
|
tests/test_footer_presence.py
|
|
92
98
|
tests/test_footer_render.py
|
|
93
|
-
tests/test_github_token.py
|
|
94
99
|
tests/test_install_script.py
|
|
95
100
|
tests/test_language_switch.py
|
|
96
101
|
tests/test_lcd_smbus2.py
|
|
@@ -101,16 +106,12 @@ tests/test_notify_command.py
|
|
|
101
106
|
tests/test_odoo_profile.py
|
|
102
107
|
tests/test_odoo_profile_admin.py
|
|
103
108
|
tests/test_offline.py
|
|
104
|
-
tests/test_package_release_admin_actions.py
|
|
105
|
-
tests/test_post_office_admin_group.py
|
|
106
109
|
tests/test_pypi_token.py
|
|
107
110
|
tests/test_readme_language.py
|
|
108
111
|
tests/test_reference_qr_code.py
|
|
109
112
|
tests/test_reference_transaction_uuid.py
|
|
110
113
|
tests/test_register_site_apps_command.py
|
|
111
|
-
tests/test_release_fixture_cleanup.py
|
|
112
114
|
tests/test_release_logs.py
|
|
113
|
-
tests/test_release_mapping.py
|
|
114
115
|
tests/test_release_progress.py
|
|
115
116
|
tests/test_release_tasks.py
|
|
116
117
|
tests/test_rfid_admin_reference_clear.py
|
|
@@ -121,4 +122,6 @@ tests/test_seed_data.py
|
|
|
121
122
|
tests/test_sigil_resolution.py
|
|
122
123
|
tests/test_urls_autodiscover.py
|
|
123
124
|
tests/test_user_datum_admin.py
|
|
124
|
-
tests/
|
|
125
|
+
tests/test_version_file.py
|
|
126
|
+
tests/test_vscode_manage.py
|
|
127
|
+
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()
|
|
@@ -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
|
|
18
|
-
return
|
|
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
|
-
|
|
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."""
|
|
@@ -148,8 +148,8 @@ INSTALLED_APPS = [
|
|
|
148
148
|
"django_object_actions",
|
|
149
149
|
"django.contrib.sites",
|
|
150
150
|
"channels",
|
|
151
|
-
"
|
|
152
|
-
"
|
|
151
|
+
"config.workgroup_app.WorkgroupConfig",
|
|
152
|
+
"config.horologia_app.HorologiaConfig",
|
|
153
153
|
] + LOCAL_APPS
|
|
154
154
|
|
|
155
155
|
if DEBUG:
|
|
@@ -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,7 @@ from .models import (
|
|
|
43
44
|
PackageRelease,
|
|
44
45
|
ReleaseManager,
|
|
45
46
|
SecurityGroup,
|
|
47
|
+
InviteLead,
|
|
46
48
|
)
|
|
47
49
|
from .user_data import UserDatumAdminMixin
|
|
48
50
|
|
|
@@ -224,6 +226,20 @@ class SecurityGroupAdmin(DjangoGroupAdmin):
|
|
|
224
226
|
filter_horizontal = ("permissions",)
|
|
225
227
|
|
|
226
228
|
|
|
229
|
+
@admin.register(InviteLead)
|
|
230
|
+
class InviteLeadAdmin(admin.ModelAdmin):
|
|
231
|
+
list_display = ("email", "created_on")
|
|
232
|
+
search_fields = ("email", "comment")
|
|
233
|
+
readonly_fields = (
|
|
234
|
+
"created_on",
|
|
235
|
+
"user",
|
|
236
|
+
"path",
|
|
237
|
+
"referer",
|
|
238
|
+
"user_agent",
|
|
239
|
+
"ip_address",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
227
243
|
class EnergyAccountRFIDForm(forms.ModelForm):
|
|
228
244
|
"""Form for assigning existing RFIDs to an energy account."""
|
|
229
245
|
|
|
@@ -784,17 +800,16 @@ class RFIDAdmin(ImportExportModelAdmin):
|
|
|
784
800
|
class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
|
|
785
801
|
list_display = (
|
|
786
802
|
"version",
|
|
787
|
-
"
|
|
803
|
+
"package_link",
|
|
788
804
|
"is_current",
|
|
789
805
|
"pypi_url",
|
|
790
|
-
"pr_link",
|
|
791
806
|
"revision_short",
|
|
792
807
|
"published_status",
|
|
793
808
|
)
|
|
794
809
|
list_display_links = ("version",)
|
|
795
|
-
actions = ["publish_release"]
|
|
810
|
+
actions = ["publish_release", "validate_releases"]
|
|
796
811
|
change_actions = ["publish_release_action"]
|
|
797
|
-
readonly_fields = ("pypi_url", "
|
|
812
|
+
readonly_fields = ("pypi_url", "is_current", "revision")
|
|
798
813
|
fields = (
|
|
799
814
|
"package",
|
|
800
815
|
"release_manager",
|
|
@@ -802,9 +817,13 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
|
|
|
802
817
|
"revision",
|
|
803
818
|
"is_current",
|
|
804
819
|
"pypi_url",
|
|
805
|
-
"pr_url",
|
|
806
820
|
)
|
|
807
821
|
|
|
822
|
+
@admin.display(description="package", ordering="package")
|
|
823
|
+
def package_link(self, obj):
|
|
824
|
+
url = reverse("admin:core_package_change", args=[obj.package_id])
|
|
825
|
+
return format_html('<a href="{}">{}</a>', url, obj.package)
|
|
826
|
+
|
|
808
827
|
def revision_short(self, obj):
|
|
809
828
|
return obj.revision_short
|
|
810
829
|
|
|
@@ -833,6 +852,37 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
|
|
|
833
852
|
publish_release_action.label = "Publish selected Release"
|
|
834
853
|
publish_release_action.short_description = "Publish this release"
|
|
835
854
|
|
|
855
|
+
@admin.action(description="Validate selected Releases")
|
|
856
|
+
def validate_releases(self, request, queryset):
|
|
857
|
+
deleted = False
|
|
858
|
+
for release in queryset:
|
|
859
|
+
if not release.pypi_url:
|
|
860
|
+
self.message_user(
|
|
861
|
+
request,
|
|
862
|
+
f"{release} has not been published yet",
|
|
863
|
+
messages.WARNING,
|
|
864
|
+
)
|
|
865
|
+
continue
|
|
866
|
+
url = (
|
|
867
|
+
f"https://pypi.org/pypi/{release.package.name}/{release.version}/json"
|
|
868
|
+
)
|
|
869
|
+
try:
|
|
870
|
+
resp = requests.get(url, timeout=10)
|
|
871
|
+
except Exception as exc: # pragma: no cover - network failure
|
|
872
|
+
self.message_user(request, f"{release}: {exc}", messages.ERROR)
|
|
873
|
+
continue
|
|
874
|
+
if resp.status_code == 200:
|
|
875
|
+
continue
|
|
876
|
+
release.delete()
|
|
877
|
+
deleted = True
|
|
878
|
+
self.message_user(
|
|
879
|
+
request,
|
|
880
|
+
f"Deleted {release} as it was not found on PyPI",
|
|
881
|
+
messages.WARNING,
|
|
882
|
+
)
|
|
883
|
+
if deleted:
|
|
884
|
+
PackageRelease.dump_fixture()
|
|
885
|
+
|
|
836
886
|
@staticmethod
|
|
837
887
|
def _boolean_icon(value: bool) -> str:
|
|
838
888
|
icon = static("admin/img/icon-yes.svg" if value else "admin/img/icon-no.svg")
|
|
@@ -847,10 +897,4 @@ class PackageReleaseAdmin(SaveBeforeChangeAction, admin.ModelAdmin):
|
|
|
847
897
|
def is_current(self, obj):
|
|
848
898
|
return self._boolean_icon(obj.is_current)
|
|
849
899
|
|
|
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
900
|
|
|
@@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
|
|
|
5
5
|
class CoreConfig(AppConfig):
|
|
6
6
|
default_auto_field = "django.db.models.BigAutoField"
|
|
7
7
|
name = "core"
|
|
8
|
-
verbose_name = _("Business
|
|
8
|
+
verbose_name = _("2. Business")
|
|
9
9
|
|
|
10
10
|
def ready(self): # pragma: no cover - called by Django
|
|
11
11
|
from django.contrib.auth import get_user_model
|
|
@@ -16,6 +16,7 @@ class CoreConfig(AppConfig):
|
|
|
16
16
|
)
|
|
17
17
|
from .system import patch_admin_system_view
|
|
18
18
|
from .environment import patch_admin_environment_view
|
|
19
|
+
from . import checks # noqa: F401
|
|
19
20
|
|
|
20
21
|
def create_default_arthexis(**kwargs):
|
|
21
22
|
User = get_user_model()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.core import checks
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _fixture_hash() -> str:
|
|
8
|
+
base_dir = Path(settings.BASE_DIR)
|
|
9
|
+
md5 = hashlib.md5()
|
|
10
|
+
for path in sorted(base_dir.glob("**/fixtures/*.json")):
|
|
11
|
+
md5.update(path.read_bytes())
|
|
12
|
+
return md5.hexdigest()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@checks.register(checks.Tags.database)
|
|
16
|
+
def check_unapplied_fixtures(app_configs=None, **kwargs):
|
|
17
|
+
"""Warn if fixture files have changed since last refresh."""
|
|
18
|
+
hash_file = Path(settings.BASE_DIR) / "fixtures.md5"
|
|
19
|
+
stored = hash_file.read_text().strip() if hash_file.exists() else ""
|
|
20
|
+
current = _fixture_hash()
|
|
21
|
+
if stored != current:
|
|
22
|
+
return [
|
|
23
|
+
checks.Warning(
|
|
24
|
+
"Unapplied fixture changes detected.",
|
|
25
|
+
hint="Run env-refresh to apply fixtures.",
|
|
26
|
+
id="core.W001",
|
|
27
|
+
)
|
|
28
|
+
]
|
|
29
|
+
return []
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import copy
|
|
2
|
+
import logging
|
|
2
3
|
import os
|
|
3
4
|
import re
|
|
4
5
|
|
|
@@ -7,6 +8,8 @@ from django.conf import settings
|
|
|
7
8
|
from django.db import models
|
|
8
9
|
from django.contrib.auth.models import UserManager as DjangoUserManager
|
|
9
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
class EntityQuerySet(models.QuerySet):
|
|
12
15
|
def delete(self): # pragma: no cover - delegates to instance delete
|
|
@@ -78,14 +81,33 @@ class Entity(models.Model):
|
|
|
78
81
|
root_name, key = match.group(1), match.group(2)
|
|
79
82
|
try:
|
|
80
83
|
root = SigilRoot.objects.get(prefix__iexact=root_name)
|
|
84
|
+
if root.context_type == SigilRoot.Context.CONFIG:
|
|
85
|
+
if root.prefix.upper() == "ENV":
|
|
86
|
+
if key in os.environ:
|
|
87
|
+
return os.environ[key]
|
|
88
|
+
logger.warning(
|
|
89
|
+
"Missing environment variable for sigil [%s.%s]",
|
|
90
|
+
root_name,
|
|
91
|
+
key,
|
|
92
|
+
)
|
|
93
|
+
return match.group(0)
|
|
94
|
+
if root.prefix.upper() == "SYS":
|
|
95
|
+
if hasattr(settings, key):
|
|
96
|
+
return str(getattr(settings, key))
|
|
97
|
+
logger.warning(
|
|
98
|
+
"Missing settings attribute for sigil [%s.%s]",
|
|
99
|
+
root_name,
|
|
100
|
+
key,
|
|
101
|
+
)
|
|
102
|
+
return match.group(0)
|
|
103
|
+
logger.warning(
|
|
104
|
+
"Unresolvable sigil [%s.%s]: unsupported context", root_name, key
|
|
105
|
+
)
|
|
81
106
|
except SigilRoot.DoesNotExist:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if root.prefix.upper() == "SYS":
|
|
87
|
-
return str(getattr(settings, key, ""))
|
|
88
|
-
return ""
|
|
107
|
+
logger.warning("Unknown sigil root [%s]", root_name)
|
|
108
|
+
except Exception:
|
|
109
|
+
logger.exception("Error resolving sigil [%s.%s]", root_name, key)
|
|
110
|
+
return match.group(0)
|
|
89
111
|
|
|
90
112
|
return pattern.sub(repl, text)
|
|
91
113
|
|
|
@@ -10,12 +10,13 @@ from django.utils.translation import gettext_lazy as _
|
|
|
10
10
|
from django.core.validators import RegexValidator
|
|
11
11
|
from django.core.exceptions import ValidationError
|
|
12
12
|
from django.apps import apps
|
|
13
|
-
from django.db.models.signals import m2m_changed
|
|
13
|
+
from django.db.models.signals import m2m_changed
|
|
14
14
|
from django.dispatch import receiver
|
|
15
15
|
from datetime import timedelta
|
|
16
16
|
from django.contrib.contenttypes.models import ContentType
|
|
17
17
|
import hashlib
|
|
18
18
|
import os
|
|
19
|
+
import subprocess
|
|
19
20
|
from io import BytesIO
|
|
20
21
|
from django.core.files.base import ContentFile
|
|
21
22
|
import qrcode
|
|
@@ -62,6 +63,34 @@ class SigilRoot(Entity):
|
|
|
62
63
|
verbose_name_plural = "Sigil Roots"
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
class Lead(models.Model):
|
|
67
|
+
"""Common request lead information."""
|
|
68
|
+
|
|
69
|
+
user = models.ForeignKey(
|
|
70
|
+
settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL
|
|
71
|
+
)
|
|
72
|
+
path = models.TextField(blank=True)
|
|
73
|
+
referer = models.TextField(blank=True)
|
|
74
|
+
user_agent = models.TextField(blank=True)
|
|
75
|
+
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
|
76
|
+
created_on = models.DateTimeField(auto_now_add=True)
|
|
77
|
+
|
|
78
|
+
class Meta:
|
|
79
|
+
abstract = True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class InviteLead(Lead):
|
|
83
|
+
email = models.EmailField()
|
|
84
|
+
comment = models.TextField(blank=True)
|
|
85
|
+
|
|
86
|
+
class Meta:
|
|
87
|
+
verbose_name = "Invite Lead"
|
|
88
|
+
verbose_name_plural = "Invite Leads"
|
|
89
|
+
|
|
90
|
+
def __str__(self) -> str: # pragma: no cover - simple representation
|
|
91
|
+
return self.email
|
|
92
|
+
|
|
93
|
+
|
|
65
94
|
class Address(Entity):
|
|
66
95
|
"""Physical location information for a user."""
|
|
67
96
|
|
|
@@ -1075,7 +1104,7 @@ class ReleaseManager(Entity):
|
|
|
1075
1104
|
max_length=200,
|
|
1076
1105
|
blank=True,
|
|
1077
1106
|
help_text=(
|
|
1078
|
-
"Personal access token
|
|
1107
|
+
"Personal access token for GitHub operations. "
|
|
1079
1108
|
"Used before the GITHUB_TOKEN environment variable."
|
|
1080
1109
|
),
|
|
1081
1110
|
)
|
|
@@ -1159,7 +1188,6 @@ class PackageRelease(Entity):
|
|
|
1159
1188
|
max_length=40, blank=True, default=revision_utils.get_revision, editable=False
|
|
1160
1189
|
)
|
|
1161
1190
|
pypi_url = models.URLField("PyPI URL", blank=True, editable=False)
|
|
1162
|
-
pr_url = models.URLField("PR URL", blank=True, editable=False)
|
|
1163
1191
|
|
|
1164
1192
|
class Meta:
|
|
1165
1193
|
verbose_name = "Package Release"
|
|
@@ -1251,17 +1279,29 @@ class PackageRelease(Entity):
|
|
|
1251
1279
|
)
|
|
1252
1280
|
self.revision = revision_utils.get_revision()
|
|
1253
1281
|
self.save(update_fields=["revision"])
|
|
1282
|
+
PackageRelease.dump_fixture()
|
|
1283
|
+
if kwargs.get("git"):
|
|
1284
|
+
diff = subprocess.run(
|
|
1285
|
+
["git", "status", "--porcelain", "core/fixtures/releases.json"],
|
|
1286
|
+
capture_output=True,
|
|
1287
|
+
text=True,
|
|
1288
|
+
)
|
|
1289
|
+
if diff.stdout.strip():
|
|
1290
|
+
release_utils._run(["git", "add", "core/fixtures/releases.json"])
|
|
1291
|
+
release_utils._run(
|
|
1292
|
+
[
|
|
1293
|
+
"git",
|
|
1294
|
+
"commit",
|
|
1295
|
+
"-m",
|
|
1296
|
+
f"chore: update release fixture for v{self.version}",
|
|
1297
|
+
]
|
|
1298
|
+
)
|
|
1299
|
+
release_utils._run(["git", "push"])
|
|
1254
1300
|
|
|
1255
1301
|
@property
|
|
1256
1302
|
def revision_short(self) -> str:
|
|
1257
1303
|
return self.revision[-6:] if self.revision else ""
|
|
1258
1304
|
|
|
1259
|
-
|
|
1260
|
-
@receiver([post_save, post_delete], sender=PackageRelease)
|
|
1261
|
-
def _update_release_fixture(sender, instance, **kwargs) -> None:
|
|
1262
|
-
"""Keep the release fixture in sync with the database."""
|
|
1263
|
-
PackageRelease.dump_fixture()
|
|
1264
|
-
|
|
1265
1305
|
# Ensure each RFID can only be linked to one energy account
|
|
1266
1306
|
@receiver(m2m_changed, sender=EnergyAccount.rfids.through)
|
|
1267
1307
|
def _rfid_unique_energy_account(sender, instance, action, reverse, model, pk_set, **kwargs):
|