arthexis 0.1.16__py3-none-any.whl → 0.1.26__py3-none-any.whl

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 (63) hide show
  1. {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/METADATA +84 -35
  2. arthexis-0.1.26.dist-info/RECORD +111 -0
  3. config/asgi.py +1 -15
  4. config/middleware.py +47 -1
  5. config/settings.py +15 -30
  6. config/urls.py +53 -1
  7. core/admin.py +540 -450
  8. core/apps.py +0 -6
  9. core/auto_upgrade.py +19 -4
  10. core/backends.py +13 -3
  11. core/changelog.py +66 -5
  12. core/environment.py +4 -5
  13. core/models.py +1566 -203
  14. core/notifications.py +1 -1
  15. core/reference_utils.py +10 -11
  16. core/release.py +55 -7
  17. core/sigil_builder.py +2 -2
  18. core/sigil_resolver.py +1 -66
  19. core/system.py +268 -2
  20. core/tasks.py +174 -48
  21. core/tests.py +314 -16
  22. core/user_data.py +42 -2
  23. core/views.py +278 -183
  24. nodes/admin.py +557 -65
  25. nodes/apps.py +11 -0
  26. nodes/models.py +658 -113
  27. nodes/rfid_sync.py +1 -1
  28. nodes/tasks.py +97 -2
  29. nodes/tests.py +1212 -116
  30. nodes/urls.py +15 -1
  31. nodes/utils.py +51 -3
  32. nodes/views.py +1239 -154
  33. ocpp/admin.py +979 -152
  34. ocpp/consumers.py +268 -28
  35. ocpp/models.py +488 -3
  36. ocpp/network.py +398 -0
  37. ocpp/store.py +6 -4
  38. ocpp/tasks.py +296 -2
  39. ocpp/test_export_import.py +1 -0
  40. ocpp/test_rfid.py +121 -4
  41. ocpp/tests.py +950 -11
  42. ocpp/transactions_io.py +9 -1
  43. ocpp/urls.py +3 -3
  44. ocpp/views.py +596 -51
  45. pages/admin.py +262 -30
  46. pages/apps.py +35 -0
  47. pages/context_processors.py +26 -21
  48. pages/defaults.py +1 -1
  49. pages/forms.py +31 -8
  50. pages/middleware.py +6 -2
  51. pages/models.py +77 -2
  52. pages/module_defaults.py +5 -5
  53. pages/site_config.py +137 -0
  54. pages/tests.py +885 -109
  55. pages/urls.py +13 -2
  56. pages/utils.py +70 -0
  57. pages/views.py +558 -55
  58. arthexis-0.1.16.dist-info/RECORD +0 -111
  59. core/workgroup_urls.py +0 -17
  60. core/workgroup_views.py +0 -94
  61. {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
  62. {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +0 -0
  63. {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arthexis
3
- Version: 0.1.16
3
+ Version: 0.1.26
4
4
  Summary: Power & Energy Infrastructure
5
5
  Author-email: "Rafael J. Guillén-Osorio" <tecnologia@gelectriic.com>
6
6
  License-Expression: GPL-3.0-only
@@ -15,7 +15,7 @@ Requires-Dist: amqp==5.3.1
15
15
  Requires-Dist: annotated-types==0.7.0
16
16
  Requires-Dist: anyio==4.9.0
17
17
  Requires-Dist: asgiref==3.10.0
18
- Requires-Dist: atproto==0.0.61
18
+ Requires-Dist: atproto<0.1.0,>=0.0.63
19
19
  Requires-Dist: attrs==25.3.0
20
20
  Requires-Dist: autobahn==24.4.2
21
21
  Requires-Dist: Automat==25.4.16
@@ -26,14 +26,14 @@ Requires-Dist: celery==5.5.3
26
26
  Requires-Dist: certifi==2025.7.14
27
27
  Requires-Dist: cffi==2.0.0
28
28
  Requires-Dist: channels==4.1.0
29
- Requires-Dist: charset-normalizer==3.4.2
29
+ Requires-Dist: charset-normalizer==3.4.4
30
30
  Requires-Dist: click==8.2.1
31
31
  Requires-Dist: click-didyoumean==0.3.1
32
32
  Requires-Dist: click-plugins==1.1.1.2
33
33
  Requires-Dist: click-repl==0.3.0
34
34
  Requires-Dist: colorama==0.4.6
35
35
  Requires-Dist: constantly==23.10.4
36
- Requires-Dist: cron-descriptor==1.4.5
36
+ Requires-Dist: cron-descriptor==2.0.6
37
37
  Requires-Dist: cryptography==45.0.5
38
38
  Requires-Dist: daphne==4.2.1
39
39
  Requires-Dist: diff-match-patch==20241021
@@ -41,46 +41,47 @@ Requires-Dist: defusedxml==0.7.1
41
41
  Requires-Dist: Django==5.2.7
42
42
  Requires-Dist: django-celery-beat==2.8.1
43
43
  Requires-Dist: django-debug-toolbar==6.0.0
44
- Requires-Dist: django-import-export==4.3.9
44
+ Requires-Dist: django-import-export==4.3.12
45
45
  Requires-Dist: django-object-actions==5.0.0
46
46
  Requires-Dist: django-otp==1.5.4
47
47
  Requires-Dist: django-timezone-field==7.1
48
48
  Requires-Dist: dnspython==2.7.0
49
49
  Requires-Dist: docutils==0.22.2
50
50
  Requires-Dist: gpiozero==2.0.1; sys_platform == "linux"
51
+ Requires-Dist: graphene-django==3.2.2
51
52
  Requires-Dist: graphviz==0.21
53
+ Requires-Dist: geckodriver-autoinstaller==0.1.0
52
54
  Requires-Dist: h11==0.16.0
53
55
  Requires-Dist: httpcore==1.0.9
54
56
  Requires-Dist: httpx==0.28.1
55
57
  Requires-Dist: hyperlink==21.0.0
56
- Requires-Dist: idna==3.10
58
+ Requires-Dist: idna==3.11
57
59
  Requires-Dist: incremental==24.7.2
58
60
  Requires-Dist: kombu==5.5.4
59
- Requires-Dist: libipld==3.1.1
60
- Requires-Dist: Markdown==3.8.2
61
+ Requires-Dist: libipld==3.2.0
62
+ Requires-Dist: Markdown==3.9
61
63
  Requires-Dist: mdx_truly_sane_lists==1.3
62
- Requires-Dist: mcp==1.16.0
63
64
  Requires-Dist: mfrc522==0.0.7; sys_platform == "linux"
64
65
  Requires-Dist: outcome==1.3.0.post0
65
66
  Requires-Dist: packaging==25.0
66
67
  Requires-Dist: pillow==11.3.0
67
68
  Requires-Dist: prompt_toolkit==3.0.51
68
- Requires-Dist: psutil==5.9.8
69
+ Requires-Dist: psutil==7.1.2
69
70
  Requires-Dist: psycopg==3.2.9
70
- Requires-Dist: psycopg-binary==3.2.9
71
+ Requires-Dist: psycopg-binary==3.2.12
71
72
  Requires-Dist: pyasn1==0.6.1
72
73
  Requires-Dist: pyasn1_modules==0.4.2
73
74
  Requires-Dist: pycparser==2.22
74
- Requires-Dist: pydantic==2.11.7
75
- Requires-Dist: pydantic_core==2.33.2
75
+ Requires-Dist: pydantic==2.12.3
76
+ Requires-Dist: pydantic_core==2.41.4
76
77
  Requires-Dist: pyOpenSSL==25.1.0
77
- Requires-Dist: pyperclip==1.9.0
78
+ Requires-Dist: pyperclip==1.11.0
78
79
  Requires-Dist: PySocks==1.7.1
79
80
  Requires-Dist: python-crontab==3.3.0
80
81
  Requires-Dist: python-dateutil==2.9.0.post0
81
82
  Requires-Dist: python-dotenv==1.1.1
82
83
  Requires-Dist: qrcode==8.2
83
- Requires-Dist: redis==5.0.8
84
+ Requires-Dist: redis==7.0.1
84
85
  Requires-Dist: reportlab==4.2.2
85
86
  Requires-Dist: requests==2.32.5
86
87
  Requires-Dist: selenium==4.34.2
@@ -99,19 +100,19 @@ Requires-Dist: trio-websocket==0.12.2
99
100
  Requires-Dist: Twisted==25.5.0
100
101
  Requires-Dist: twine==6.1.0
101
102
  Requires-Dist: txaio==25.6.1
102
- Requires-Dist: typing-inspection==0.4.1
103
+ Requires-Dist: typing-inspection==0.4.2
103
104
  Requires-Dist: typing_extensions==4.14.1
104
105
  Requires-Dist: tzdata==2025.2
105
106
  Requires-Dist: urllib3==2.5.0
106
107
  Requires-Dist: vine==5.1.0
107
- Requires-Dist: wcwidth==0.2.13
108
+ Requires-Dist: wcwidth==0.2.14
108
109
  Requires-Dist: webencodings==0.5.1
109
110
  Requires-Dist: websocket-client==1.8.0
110
- Requires-Dist: websockets==13.1
111
- Requires-Dist: whitenoise==6.9.0
111
+ Requires-Dist: websockets==15.0.1
112
+ Requires-Dist: whitenoise==6.11.0
112
113
  Requires-Dist: plyer==2.1.0; sys_platform == "win32"
113
114
  Requires-Dist: wsproto==1.2.0
114
- Requires-Dist: zope.interface==7.2
115
+ Requires-Dist: zope.interface==8.0.1
115
116
  Dynamic: license-file
116
117
 
117
118
  # Arthexis Constellation
@@ -125,19 +126,39 @@ Arthexis Constellation is a [narrative-driven](https://en.wikipedia.org/wiki/Nar
125
126
 
126
127
  ## Current Features
127
128
 
128
- - Compatible with the [Open Charge Point Protocol (OCPP) 1.6](https://www.openchargealliance.org/protocols/ocpp-16/) central system, handling:
129
- - Lifecycle & sessions
130
- - `BootNotification`
131
- - `Heartbeat`
132
- - `StatusNotification`
133
- - `StartTransaction`
134
- - `StopTransaction`
135
- - Access & metering
136
- - `Authorize`
137
- - `MeterValues`
138
- - Maintenance & firmware
139
- - `DiagnosticsStatusNotification`
140
- - `FirmwareStatusNotification`
129
+ - Compatible with the [Open Charge Point Protocol (OCPP) 1.6](https://www.openchargealliance.org/protocols/ocpp-16/) central system. Supported actions are summarized below.
130
+
131
+ **Charge point → CSMS**
132
+
133
+ | Action | What we do |
134
+ | --- | --- |
135
+ | `Authorize` | Validate RFID or token authorization requests before a session starts. |
136
+ | `BootNotification` | Register the charge point and update identity, firmware, and status details. |
137
+ | `DataTransfer` | Accept vendor-specific payloads and record the results. |
138
+ | `DiagnosticsStatusNotification` | Track the progress of diagnostic uploads kicked off from the back office. |
139
+ | `FirmwareStatusNotification` | Track firmware update lifecycle events from charge points. |
140
+ | `Heartbeat` | Keep the websocket session alive and update last-seen timestamps. |
141
+ | `MeterValues` | Persist periodic energy and power readings while a transaction is active. |
142
+ | `StartTransaction` | Create charging sessions with initial meter values and identification data. |
143
+ | `StatusNotification` | Reflect connector availability and fault states in real time. |
144
+ | `StopTransaction` | Close charging sessions, capturing closing meter values and stop reasons. |
145
+
146
+ **CSMS → Charge point**
147
+
148
+ | Action | What we do |
149
+ | --- | --- |
150
+ | `ChangeAvailability` | Switch connectors or the whole station between operative and inoperative states. |
151
+ | `DataTransfer` | Send vendor-specific commands and log the charge point response. |
152
+ | `GetConfiguration` | Poll the device for the current values of tracked configuration keys. |
153
+ | `RemoteStartTransaction` | Initiate a charging session remotely for an identified customer or token. |
154
+ | `RemoteStopTransaction` | Terminate active charging sessions from the control center. |
155
+ | `ReserveNow` | Reserve connectors for upcoming sessions with automatic connector selection and confirmation tracking. |
156
+ | `Reset` | Request a soft or hard reboot to recover from faults. |
157
+ | `TriggerMessage` | Ask the device to send an immediate update (for example status or diagnostics). |
158
+
159
+ **OCPP 1.6 roadmap.** The following catalogue actions are in our backlog: `CancelReservation`, `ChangeConfiguration`, `ClearCache`, `ClearChargingProfile`, `GetCompositeSchedule`, `GetDiagnostics`, `GetLocalListVersion`, `SendLocalList`, `SetChargingProfile`, `UnlockConnector`, `UpdateFirmware`.
160
+
161
+ - Charge point reservations with automated connector assignment, energy account and RFID linkage, and EVCS confirmation tracking.
141
162
  - [API](https://en.wikipedia.org/wiki/API) integration with [Odoo](https://www.odoo.com/), syncing:
142
163
  - Employee credentials via `res.users`
143
164
  - Product catalog lookups via `product.product`
@@ -171,7 +192,7 @@ Arthexis Constellation ships in four node roles tailored to different deployment
171
192
  <td valign="top"><strong>Multi-Device Edge, Network &amp; Data Acquisition</strong><br />Features: AP Router, Celery Queue, NGINX Server, RFID Scanner</td>
172
193
  </tr>
173
194
  <tr>
174
- <td valign="top"><strong>Constellation</strong></td>
195
+ <td valign="top"><strong>Watchtower</strong></td>
175
196
  <td valign="top"><strong>Multi-User Cloud &amp; Orchestration</strong><br />Features: Celery Queue, NGINX Server</td>
176
197
  </tr>
177
198
  </tbody>
@@ -184,7 +205,7 @@ Arthexis Constellation ships in four node roles tailored to different deployment
184
205
  - **[Windows](https://en.wikipedia.org/wiki/Microsoft_Windows)**: open [PowerShell](https://learn.microsoft.com/powershell/) or [Git Bash](https://gitforwindows.org/) and run the same command.
185
206
 
186
207
  ### 2. Start and stop
187
- Terminal nodes can start directly with the scripts below without installing; Control, Satellite, and Constellation roles require installation first. Both approaches listen on [`http://localhost:8000/`](http://localhost:8000/) by default.
208
+ Terminal nodes can start directly with the scripts below without installing; Control, Satellite, and Watchtower roles require installation first. Both approaches listen on [`http://localhost:8000/`](http://localhost:8000/) by default.
188
209
 
189
210
  - **[VS Code](https://code.visualstudio.com/)**
190
211
  - Open the folder and go to the **Run and Debug** panel (`Ctrl+Shift+D`).
@@ -204,6 +225,7 @@ Terminal nodes can start directly with the scripts below without installing; Con
204
225
  - `--constellation` – enables the multi-user orchestration stack.
205
226
  - Use `./install.sh --help` to list every available flag if you need to customize the node beyond the role defaults.
206
227
  - Upgrade with [`./upgrade.sh`](upgrade.sh).
228
+ - Consult the [Install & Lifecycle Scripts Manual](docs/development/install-lifecycle-scripts-manual.md) for complete flag descriptions and operational notes.
207
229
 
208
230
  - **Windows:**
209
231
  - Run [`install.bat`](install.bat) to install (Terminal role) and [`upgrade.bat`](upgrade.bat) to upgrade.
@@ -212,10 +234,37 @@ Terminal nodes can start directly with the scripts below without installing; Con
212
234
  ### 4. Administration
213
235
  Visit [`http://localhost:8000/admin/`](http://localhost:8000/admin/) for the [Django admin](https://docs.djangoproject.com/en/stable/ref/contrib/admin/) and [`http://localhost:8000/admindocs/`](http://localhost:8000/admindocs/) for the [admindocs](https://docs.djangoproject.com/en/stable/ref/contrib/admin/admindocs/). Use `--port` with the start scripts or installer when you need to expose a different port.
214
236
 
237
+ ## Sigils
238
+
239
+ Sigils are bracketed tokens such as `[ENV.SMTP_PASSWORD]` that Arthexis expands at runtime. They make it possible to reference configuration secrets, system metadata, or records stored in other apps without duplicating values across the project.
240
+
241
+ ### Syntax at a glance
242
+
243
+ - `[PREFIX.KEY]` &mdash; returns a field or attribute. Hyphens and casing are normalized automatically.
244
+ - `[PREFIX=IDENTIFIER.FIELD]` &mdash; selects a specific record by primary key or any unique field.
245
+ - `[PREFIX:FIELD=VALUE.ATTRIBUTE]` &mdash; filters by a custom field instead of the primary key.
246
+ - `[PREFIX.FIELD=[OTHER.SIGIL]]` &mdash; nests sigils so the value after `=` resolves before the outer token.
247
+ - `[PREFIX]` &mdash; for entity prefixes, returns the serialized object in JSON; for configuration prefixes, resolves to an empty string when the key is missing.
248
+
249
+ The platform ships with three configuration prefixes:
250
+
251
+ - `ENV` reads environment variables.
252
+ - `CONF` reads Django settings.
253
+ - `SYS` exposes computed system information such as build metadata.
254
+
255
+ Additional prefixes are defined through **Sigil Roots**, which map a short code (for example `ROLE`, `ODOO`, or `USER`) to a Django model. You can review them from **Admin &rarr; Sigil Builder** (`/admin/sigil-builder/`), where a test console is also available.
256
+
257
+ Unknown prefixes remain in place (e.g. `[UNKNOWN.VALUE]`) and are logged.
258
+
215
259
  ## Support
216
260
 
217
261
  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).
218
262
 
263
+ ## Project Guidelines
264
+
265
+ - [AGENTS](AGENTS.md) – operating handbook for repository workflows, testing, and release management.
266
+ - [DESIGN](DESIGN.md) – visual, UX, and branding guidance that all interfaces must follow.
267
+
219
268
  ## About Me
220
269
 
221
270
  > "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."
@@ -0,0 +1,111 @@
1
+ arthexis-0.1.26.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
2
+ config/__init__.py,sha256=AwpOX7il-DAOmkdJ5dVfVJ3CWWebn1lHyQNmkw1EkDw,103
3
+ config/active_app.py,sha256=KJqYh-o91nPQjVXPEdbiJHzsI6cN9IZsBZ9O3iZ6Hyc,373
4
+ config/asgi.py,sha256=Z2HjWrxOxVU9BXcqS7dMEfOGJC48H-WPwFwokRdermY,774
5
+ config/auth_app.py,sha256=cLlKgFYV4VWsMACinKrpP-nZhXXbWZp1aj1RayDszRc,198
6
+ config/celery.py,sha256=c8fPkjfhKw0UQBr6FolzfdvRX2MQAV-dMokSlJ1Afgg,819
7
+ config/context_processors.py,sha256=p74ocuzPRFI9vKSeIaJ42Vu0V2GtGph1t-2DkRo4NMw,2449
8
+ config/horologia_app.py,sha256=puO_hObEYcLvE7PqcY_sGv1thnxJ018YKHKZWqNXha4,187
9
+ config/loadenv.py,sha256=CjXx-wBaTt1wixub4GJ5CMSMFqtiK5JURc7cPXpqO7s,287
10
+ config/logging.py,sha256=1cIbPgRshHuMKnVEEH0jKpRAlJSpewvLFbYDz7sCBG4,2104
11
+ config/middleware.py,sha256=zF8Cma0n5G8NNdh2LVeNJi7Hgl1G4mF9msRE2eRi1RU,2328
12
+ config/offline.py,sha256=X-yDcyoI4C44Y27lpkUwszY_09GwwFfazEsthKJpQ70,1382
13
+ config/settings.py,sha256=1vKOC0VwPOMXXnpUr4OIDRS1C_l_PXY-yyiDo7jmflM,20970
14
+ config/settings_helpers.py,sha256=0BdBciUHIkwsWa0vV_RKAd4wDuEzgE7G-42XYiES4YQ,3127
15
+ config/urls.py,sha256=RtoLsMBdsuKi_hxrlUW3GFCtiAZI_zGxrgXCxqpVVYE,6949
16
+ config/wsgi.py,sha256=zU_mKlya6hejQ21PxKacTui3dUWd4ca_-YJNSYAoMX0,433
17
+ core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ core/admin.py,sha256=IT9nfHKNRWSMmQeCVtCaNCU--aLdAGkViwIx-MZaZJ8,145219
19
+ core/admin_history.py,sha256=XZ4b0ryufIka-xcwboK3DzmOL-INSx5Y2fJO-aJdV70,1783
20
+ core/admindocs.py,sha256=ycD0bJ_VE6rTGf9ebXTiKdYkD8Y8hD2oQ4HxxoBURCM,6756
21
+ core/apps.py,sha256=S6fySxtxUzfvz8FI9dii0KI4wSyLhh5API_oeERLIsc,14084
22
+ core/auto_upgrade.py,sha256=8JpTWPH3-bQpmtOnTa5h3wdTVAbdthr0IqJze4deDUk,2242
23
+ core/backends.py,sha256=O6QzNsX3OXi0QeIO7PCXI57VLz7HoY72ouBGTpzsSPM,10793
24
+ core/changelog.py,sha256=SRn37i5N-qb-RYV4Gpu9fg7Kv8gu4TH8ZwEmDRgN-Vo,12594
25
+ core/entity.py,sha256=o4VteOXePGEsIWJFZ3fpq3DZsdWr3hpQ9A6kFbKosSE,4844
26
+ core/environment.py,sha256=QoiMjFVmwCEln2DEa10Hht6Ymop0H6wbDsyO6-ebXp4,1651
27
+ core/fields.py,sha256=d-qGahdcv4SRcO4fwCJ6_-NnEAP5xW0k3kODdAAAHSA,5412
28
+ core/form_fields.py,sha256=h2xT8sO8EWbznsiARkxukFk69yoW6mQwqpgonA-d6aA,2496
29
+ core/github_helper.py,sha256=fkjoUPwOB19zbGuk39LNLJ5AbIVKFf3rNCtnu-JISIc,5733
30
+ core/github_issues.py,sha256=qIygOk1ZCki0eB-9o1poJ2BnaMnbHH4ewVE36hqHUuo,5223
31
+ core/github_repos.py,sha256=8KCxcEiO2Ltgde7UDTAFOyHTm_eBeZYUIZegEbrjkWA,1690
32
+ core/lcd_screen.py,sha256=WtHMlSoZXKOsdM0d-v-f8ul-LSA6FA1bEWFwho1t6s8,2573
33
+ core/liveupdate.py,sha256=22m0ueQ10-6b-9pQJHY0_5WRYA98fysXKEXOWzIr550,691
34
+ core/log_paths.py,sha256=lxvgXPgJtVNZ-kYrqV8VFle4GFQrSxG-yRTglqvclmU,3318
35
+ core/mailer.py,sha256=JpW0RnD9uZ4O-wvlqeW7CMw95IFeCSkdvbankJDwHq0,2886
36
+ core/middleware.py,sha256=j19K9SX-Emkv7BDDtAacR9g6RWsxhKHwCc8w23JFvMM,3388
37
+ core/models.py,sha256=-vZjeIEFplX0uvFuh0Ctl1oxYLvx0GOvBvSZa1zZwe0,174650
38
+ core/notifications.py,sha256=jNLSuSCrhb8x5cDu_APeDlkrmbMejufk5eJOhssAC4I,3917
39
+ core/public_wifi.py,sha256=yydLgxOo9DmJJbM4X_23wGR3gxL3YzHno54v9GssuFA,7213
40
+ core/reference_utils.py,sha256=tffCoyE1w4_SmYzXVWOsW8aR_ZVVTSPzrGhBq8K2xzA,3631
41
+ core/release.py,sha256=tEMcM7qubmFGmER3TD_MG5j4RceHcwtBjczArZWXEWE,31357
42
+ core/rfid_import_export.py,sha256=petyhPvL0WUpehc6uGUDUhjYQ9AVvc6O49zuhDs6YFw,3516
43
+ core/sigil_builder.py,sha256=SmmYRjl11u2GpU8rt3va-TP5hwAOobJTWTUGqVAikDk,4854
44
+ core/sigil_context.py,sha256=GCzjfM6fcVvBtSbVNfmE6sx3HU8QnxnXrCIytnNpQzM,439
45
+ core/sigil_resolver.py,sha256=06Zt4g3KCJSeQJAg_NYCIbXhNejpfFclosweur0C4W4,9182
46
+ core/system.py,sha256=RVQA66t43Vt-jn5jIZUzYZ63FANs4onk8bXNJRl-rWo,44104
47
+ core/tasks.py,sha256=nhXLn_kE5r7VMraMIG22tBNgKrVTMnvUpuVyHlmUoHg,16176
48
+ core/temp_passwords.py,sha256=FieUnIUeQHmA1DoXvfJ5U6-Ayv3oDz-hSln5s_vNbA4,5271
49
+ core/test_system_info.py,sha256=IMPz21KEs6OC5YbL7YaIBdmJVLjRY6MgPuZpldJB5OI,6935
50
+ core/tests.py,sha256=PuxoarDS4reHNV4EDIyVRW7xIOFxZJYou1K_LI9ZNHY,105265
51
+ core/tests_liveupdate.py,sha256=IquU8ztk6zbzC1bQu3Nrr3RzGzuujtPwDkANJHbxg98,510
52
+ core/urls.py,sha256=YPippON1MAP2KeZZ8jHpcLO6mvbnKn1q7fdMv5Vm9dY,425
53
+ core/user_data.py,sha256=4pheHB5RqLJtmWMql30CLaCpuVqSyShXb7Sy-crRk_4,22400
54
+ core/views.py,sha256=s_a2tu9xwOpVSo7vo0iDDV4RN23jxGZNR-o7JNHs1Wk,88256
55
+ core/widgets.py,sha256=vlR9PlFfZGlkHm5X2cqNXuEBZSj8gmWaR6MO1mMy6kg,6904
56
+ nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ nodes/admin.py,sha256=H4wgxrVOvMhtFcl4JbLsyLklaM8WUAjS1xdF0ROkGAA,79911
58
+ nodes/apps.py,sha256=oi_M2Ya8CAR8N_MoYU68u7_9u-9SlIMelzLOgYM9tDs,3059
59
+ nodes/backends.py,sha256=dmmbS0X2YIlCDz2KjoDf_L62dy--nuqZF1rEDoi2JHM,5921
60
+ nodes/dns.py,sha256=D5smXD7Rkh6E4MdL6TBL2WY8GgJg7Rx9z88LZrcMbTw,7048
61
+ nodes/feature_checks.py,sha256=27e4PCkZ8BGWnJCOwMcY2Bo9z7LoeZWiTZuISWGnrzk,3996
62
+ nodes/lcd.py,sha256=iKA8Wmq85KZD52aTzAU8ZmS144_gbdGMOXcE8yuECps,5758
63
+ nodes/models.py,sha256=x_s7b2smjEsHE6O-fAz6u8gPxXM5QzwaswhLp56nm1E,83613
64
+ nodes/reports.py,sha256=NRYh3Y0SlZFhx31Zh2K03yO12ZrpxEHEY6T-dODA6WE,12059
65
+ nodes/rfid_sync.py,sha256=oeblawcp6xeLApdIuhsJS83OAk58Eu7pVVmgpAc0Nt8,6953
66
+ nodes/signals.py,sha256=PtOKdQfb08mV1LgSZvn7ZAcfOyy2c3Xkq4AOpBQyUdE,622
67
+ nodes/tasks.py,sha256=TKSUE4eILV644iPtn2Xr_UL3ZFYgzjzSAIGUYmhg3Sk,5111
68
+ nodes/tests.py,sha256=-cnhtAh8bWepy9RZU_j_fFROen9GNLe-bQI4qLyngPk,199544
69
+ nodes/urls.py,sha256=9uMhDq-b-EWZz0u-NvPRVSPXJeXfuS-BAACvsCs6gaE,1267
70
+ nodes/utils.py,sha256=x7l8Fz8UwWBumeNrHemmLXXRW_5bkMHaaRnE-VLZFwo,6337
71
+ nodes/views.py,sha256=4uktDPCpkN779Uj5LSnH1PSx4cNim7UfUz8q9eX69NI,60081
72
+ ocpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
+ ocpp/admin.py,sha256=BSfS4bwqxcC0a57xXOyIw9hFRxB0y7bLsF2gP5rRMfg,60175
74
+ ocpp/apps.py,sha256=i3NqrmIamNEQBT33CIqh7HOSOPmJXCMKrZ-DUd3whqg,842
75
+ ocpp/consumers.py,sha256=XfrtwJR8OLvYzxj0NIIEa_ky36UBjHROUtoEFqFK39I,75577
76
+ ocpp/evcs.py,sha256=q1mZrCVSZxXTrtYsDqH6lkeEcJ6tfSC7p9YxkDmpSCw,28883
77
+ ocpp/evcs_discovery.py,sha256=OmrzgaOHwveDRJs8AIhrM3apX8_k2PPXh_oYaYpNW3c,3876
78
+ ocpp/models.py,sha256=z5uy3zuTCICkrIg7rvBdnnYJzwHUyFrUP1AE-8LX8mk,47889
79
+ ocpp/network.py,sha256=N3je0wXckSqlHLJNQazpxrBvv0yAR7DdjfAR-hTcWDk,14149
80
+ ocpp/reference_utils.py,sha256=_UR82GfE93kv4766mHyVIfdhhyYvrT59660r3H6W55M,1072
81
+ ocpp/routing.py,sha256=3kQya-MdJ00778xDmX0esQLBP05P200V45asg-CGNoo,438
82
+ ocpp/simulator.py,sha256=vnyd59QffT79AaPhmfM_jipni_nqfG57X5tXyx1rBoc,28016
83
+ ocpp/status_display.py,sha256=YGFosd5HJETA0DcLdsjvx6EfhZSnI8Aa3cMnHG2WsBE,939
84
+ ocpp/store.py,sha256=gLCSaP9KKF7li2ALlE3O3RW5eVJtoe-_YHfKhdf0VOM,18943
85
+ ocpp/tasks.py,sha256=AgxSpWQtal6va_KbJsoNEOWug3NRn571ycwsSmwuJC4,15203
86
+ ocpp/test_export_import.py,sha256=ouQbTCp4mxfqoK6gondlu3PPcyrT9jSbWAX5gqqgaNk,4561
87
+ ocpp/test_rfid.py,sha256=IhFSlvsI8A8D3S32sRE298nYfrmqxbv7GfVErtNU3DQ,39137
88
+ ocpp/tests.py,sha256=pEVega4N2gJctjOiO8CSvMqGKrcrUtZrLSLBNSigRGU,214558
89
+ ocpp/transactions_io.py,sha256=p2aUsKlCDYnZ4ZBrOM7pxXoW_w3Tbm-tvRFSjnR3x24,7738
90
+ ocpp/urls.py,sha256=5ZomUtznJe3kfs8E-DtVp12eFva5jUuJdpTEczIsQ5w,1730
91
+ ocpp/views.py,sha256=6p-xSN6umchsDt-Vub_rUeykC5mWAWRnV4AchkrU0wM,77246
92
+ pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
+ pages/admin.py,sha256=VbxkwgjrFx7lXVhwbZuPSvCkS7EC-flBpwOztHejWtE,35834
94
+ pages/apps.py,sha256=0qcTFKVX9_QgqexJtGeph1sHRqq7khJf4x5ZtkWwblg,1424
95
+ pages/checks.py,sha256=sM8_hUVM_HOIocvtTb2sY3AaSEvbTnOlO46UchGVd-0,1527
96
+ pages/context_processors.py,sha256=vrgMu4vYCOonZ8eZ27gQvGU74PBpMi47T512Lu1__sA,5297
97
+ pages/defaults.py,sha256=3tjv3nFPxwpFu6poJ1Ez1MP92Q6ZvyRluftKHlU-zeI,522
98
+ pages/forms.py,sha256=r3JM5qp3_4RR01-u6XV8WDOaeiRe4OvCN8Y52FcsAwI,7909
99
+ pages/middleware.py,sha256=-tXFju1siXvzVsHcgjClfTtryw-5-PwW0171DQQxKu4,7115
100
+ pages/models.py,sha256=9LdIoIK2Epp3YDUk8LUWyhLW5pJ-NiuYTzO_-xKjg0c,23636
101
+ pages/module_defaults.py,sha256=rCAY8aTyxYNL0M5zDr393rX-Gi-svXqKtuLXm0rILrQ,5444
102
+ pages/site_config.py,sha256=f1Me0GFdHeGbIeyMlQNzD2e6hym59YHqbz92U_ppffY,4057
103
+ pages/tasks.py,sha256=ivcba_3wSQ1-cku0oDplzw6vLeQ9hBq3R4TG-LmR5gs,1913
104
+ pages/tests.py,sha256=_bVEijMfjVq46hNeGRDZprbQXAAUyS2LhXqZ5_Tkryg,155739
105
+ pages/urls.py,sha256=Oe88tm67iVHRFcGJLSBidZ0rkRQPRZ_vRt6ahxNqPek,1499
106
+ pages/utils.py,sha256=vEFrXSzN-3wsK2H687_oVKSwsSOP_NB7DXg1hHwHink,2471
107
+ pages/views.py,sha256=Yd7JRD0OQhhvYsYZLVDUxJz9zjba84jLmyhZ1K1RE0w,65286
108
+ arthexis-0.1.26.dist-info/METADATA,sha256=F6dYnw0KaarTV4KJZ19kzT5p9WR2lMNLewUNSEpEumQ,13888
109
+ arthexis-0.1.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
110
+ arthexis-0.1.26.dist-info/top_level.txt,sha256=J2a2q8_BWrCZ8H2WFUNMBfO2jz8j2gax6zZh-_1QDac,29
111
+ arthexis-0.1.26.dist-info/RECORD,,
config/asgi.py CHANGED
@@ -9,35 +9,21 @@ https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
9
9
 
10
10
  import os
11
11
  from config.loadenv import loadenv
12
- from typing import Any, Awaitable, Callable, Dict, MutableMapping
13
12
  from channels.auth import AuthMiddlewareStack
14
13
  from channels.routing import ProtocolTypeRouter, URLRouter
15
14
  from django.core.asgi import get_asgi_application
16
15
  import ocpp.routing
17
16
 
18
- from core.mcp.asgi import application as mcp_application
19
- from core.mcp.asgi import is_mcp_scope
20
-
21
17
  loadenv()
22
18
  os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
23
19
 
24
20
  django_asgi_app = get_asgi_application()
25
21
 
26
- Scope = MutableMapping[str, Any]
27
- Receive = Callable[[], Awaitable[Dict[str, Any]]]
28
- Send = Callable[[Dict[str, Any]], Awaitable[None]]
29
-
30
22
  websocket_patterns = ocpp.routing.websocket_urlpatterns
31
23
 
32
- async def http_application(scope: Scope, receive: Receive, send: Send) -> None:
33
- if is_mcp_scope(scope):
34
- await mcp_application(scope, receive, send)
35
- else:
36
- await django_asgi_app(scope, receive, send)
37
-
38
24
  application = ProtocolTypeRouter(
39
25
  {
40
- "http": http_application,
26
+ "http": django_asgi_app,
41
27
  "websocket": AuthMiddlewareStack(URLRouter(websocket_patterns)),
42
28
  }
43
29
  )
config/middleware.py CHANGED
@@ -1,6 +1,8 @@
1
- from utils.sites import get_site
2
1
  import socket
2
+ from django.core.exceptions import DisallowedHost
3
+ from django.http import HttpResponsePermanentRedirect
3
4
  from nodes.models import Node
5
+ from utils.sites import get_site
4
6
 
5
7
  from .active_app import set_active_app
6
8
 
@@ -17,9 +19,53 @@ class ActiveAppMiddleware:
17
19
  role_name = node.role.name if node and node.role else "Terminal"
18
20
  active = site.name or role_name
19
21
  set_active_app(active)
22
+ request.site = site
20
23
  request.active_app = active
21
24
  try:
22
25
  response = self.get_response(request)
23
26
  finally:
24
27
  set_active_app(socket.gethostname())
25
28
  return response
29
+
30
+
31
+ def _is_https_request(request) -> bool:
32
+ if request.is_secure():
33
+ return True
34
+
35
+ forwarded_proto = request.META.get("HTTP_X_FORWARDED_PROTO", "")
36
+ if forwarded_proto:
37
+ candidate = forwarded_proto.split(",")[0].strip().lower()
38
+ if candidate == "https":
39
+ return True
40
+
41
+ forwarded_header = request.META.get("HTTP_FORWARDED", "")
42
+ for forwarded_part in forwarded_header.split(","):
43
+ for element in forwarded_part.split(";"):
44
+ key, _, value = element.partition("=")
45
+ if key.strip().lower() == "proto" and value.strip().strip('"').lower() == "https":
46
+ return True
47
+
48
+ return False
49
+
50
+
51
+ class SiteHttpsRedirectMiddleware:
52
+ """Redirect HTTP traffic to HTTPS for sites that require it."""
53
+
54
+ def __init__(self, get_response):
55
+ self.get_response = get_response
56
+
57
+ def __call__(self, request):
58
+ site = getattr(request, "site", None)
59
+ if site is None:
60
+ site = get_site(request)
61
+ request.site = site
62
+
63
+ if getattr(site, "require_https", False) and not _is_https_request(request):
64
+ try:
65
+ host = request.get_host()
66
+ except DisallowedHost: # pragma: no cover - defensive guard
67
+ host = request.META.get("HTTP_HOST", "")
68
+ redirect_url = f"https://{host}{request.get_full_path()}"
69
+ return HttpResponsePermanentRedirect(redirect_url)
70
+
71
+ return self.get_response(request)
config/settings.py CHANGED
@@ -18,6 +18,8 @@ import ipaddress
18
18
  import socket
19
19
  from core.log_paths import select_log_dir
20
20
  from django.utils.translation import gettext_lazy as _
21
+ from datetime import timedelta
22
+
21
23
  from celery.schedules import crontab
22
24
  from django.http import request as http_request
23
25
  from django.http.request import split_domain_port
@@ -333,6 +335,7 @@ CsrfViewMiddleware._check_referer = _check_referer_with_forwarded
333
335
  # Application definition
334
336
 
335
337
  LOCAL_APPS = [
338
+ "api",
336
339
  "nodes",
337
340
  "core",
338
341
  "ocpp",
@@ -390,6 +393,7 @@ MIDDLEWARE = [
390
393
  "whitenoise.middleware.WhiteNoiseMiddleware",
391
394
  "django.contrib.sessions.middleware.SessionMiddleware",
392
395
  "config.middleware.ActiveAppMiddleware",
396
+ "config.middleware.SiteHttpsRedirectMiddleware",
393
397
  "django.middleware.locale.LocaleMiddleware",
394
398
  "django.middleware.common.CommonMiddleware",
395
399
  "django.middleware.csrf.CsrfViewMiddleware",
@@ -443,32 +447,6 @@ ASGI_APPLICATION = "config.asgi.application"
443
447
  CHANNEL_LAYERS = {"default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}}
444
448
 
445
449
 
446
- # MCP sigil resolver configuration
447
- def _env_int(name: str, default: int) -> int:
448
- try:
449
- return int(os.environ.get(name, default))
450
- except (TypeError, ValueError): # pragma: no cover - defensive
451
- return default
452
-
453
-
454
- def _split_env_list(name: str) -> list[str]:
455
- raw = os.environ.get(name)
456
- if not raw:
457
- return []
458
- return [item.strip() for item in raw.split(",") if item.strip()]
459
-
460
-
461
- MCP_SIGIL_SERVER = {
462
- "host": os.environ.get("MCP_SIGIL_HOST", "127.0.0.1"),
463
- "port": _env_int("MCP_SIGIL_PORT", 8800),
464
- "api_keys": _split_env_list("MCP_SIGIL_API_KEYS"),
465
- "required_scopes": ["sigils:read"],
466
- "issuer_url": os.environ.get("MCP_SIGIL_ISSUER_URL"),
467
- "resource_server_url": os.environ.get("MCP_SIGIL_RESOURCE_URL"),
468
- "mount_path": os.environ.get("MCP_SIGIL_MOUNT_PATH", "/mcp"),
469
- }
470
-
471
-
472
450
  # Custom user model
473
451
  AUTH_USER_MODEL = "core.User"
474
452
 
@@ -480,6 +458,9 @@ AUTHENTICATION_BACKENDS = [
480
458
  "core.backends.RFIDBackend",
481
459
  ]
482
460
 
461
+ # Use the custom login view for all authentication redirects.
462
+ LOGIN_URL = "pages:login"
463
+
483
464
  # Issuer name used when generating otpauth URLs for authenticator apps.
484
465
  OTP_TOTP_ISSUER = os.environ.get("OTP_TOTP_ISSUER", "Arthexis")
485
466
 
@@ -581,7 +562,7 @@ AUTH_PASSWORD_VALIDATORS = [
581
562
  LANGUAGE_CODE = "en-us"
582
563
 
583
564
  LANGUAGES = [
584
- ("es", _("Spanish")),
565
+ ("es", _("Spanish (Latin America)")),
585
566
  ("en", _("English")),
586
567
  ("it", _("Italian")),
587
568
  ("de", _("German")),
@@ -684,8 +665,12 @@ CELERY_BEAT_SCHEDULE = {
684
665
  "task": "core.tasks.heartbeat",
685
666
  "schedule": crontab(minute="*/5"),
686
667
  },
687
- "birthday_greetings": {
688
- "task": "core.tasks.birthday_greetings",
689
- "schedule": crontab(hour=9, minute=0),
668
+ "ocpp_configuration_check": {
669
+ "task": "ocpp.tasks.schedule_daily_charge_point_configuration_checks",
670
+ "schedule": crontab(minute=0, hour=0),
671
+ },
672
+ "ocpp_forwarding_push": {
673
+ "task": "ocpp.tasks.push_forwarded_charge_points",
674
+ "schedule": timedelta(seconds=5),
690
675
  },
691
676
  }
config/urls.py CHANGED
@@ -14,6 +14,8 @@ from django.conf import settings
14
14
  from django.conf.urls.static import static
15
15
  from django.contrib.staticfiles.urls import staticfiles_urlpatterns
16
16
  from django.contrib import admin
17
+ from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
18
+ from django.db.utils import DatabaseError, OperationalError, ProgrammingError
17
19
  from django.urls import include, path
18
20
  import teams.admin # noqa: F401
19
21
  from django.views.decorators.csrf import csrf_exempt
@@ -28,6 +30,44 @@ from core.admindocs import (
28
30
  )
29
31
  from pages import views as pages_views
30
32
 
33
+ try: # Gate optional GraphQL dependency for roles that do not install it
34
+ from api.views import EnergyGraphQLView
35
+ except ModuleNotFoundError as exc: # pragma: no cover - dependency intentionally optional
36
+ if exc.name in {"graphene_django", "graphene"}:
37
+ EnergyGraphQLView = None # type: ignore[assignment]
38
+ else: # pragma: no cover - unrelated import error
39
+ raise
40
+
41
+
42
+ def _graphql_feature_enabled() -> bool:
43
+ """Return ``True`` when the GraphQL endpoint should be exposed."""
44
+
45
+ if EnergyGraphQLView is None:
46
+ return False
47
+
48
+ try:
49
+ from nodes.models import Node, NodeFeature
50
+ except (ModuleNotFoundError, AppRegistryNotReady, ImproperlyConfigured):
51
+ return True
52
+
53
+ try:
54
+ feature = NodeFeature.objects.filter(slug="graphql").first()
55
+ except (DatabaseError, OperationalError, ProgrammingError):
56
+ return True
57
+
58
+ if feature is None:
59
+ return True
60
+
61
+ try:
62
+ node = Node.get_local()
63
+ except (DatabaseError, OperationalError, ProgrammingError):
64
+ return True
65
+
66
+ if node and not node.has_feature("graphql"):
67
+ return False
68
+
69
+ return True
70
+
31
71
  admin.site.site_header = _("Constellation")
32
72
  admin.site.site_title = _("Constellation")
33
73
 
@@ -134,6 +174,11 @@ urlpatterns = [
134
174
  core_views.todo_done,
135
175
  name="todo-done",
136
176
  ),
177
+ path(
178
+ "admin/core/todos/<int:pk>/delete/",
179
+ core_views.todo_delete,
180
+ name="todo-delete",
181
+ ),
137
182
  path(
138
183
  "admin/core/todos/<int:pk>/snapshot/",
139
184
  core_views.todo_snapshot,
@@ -149,12 +194,19 @@ urlpatterns = [
149
194
  core_views.odoo_quote_report,
150
195
  name="odoo-quote-report",
151
196
  ),
197
+ path(
198
+ "admin/request-temp-password/",
199
+ core_views.request_temp_password,
200
+ name="admin-request-temp-password",
201
+ ),
152
202
  path("admin/", admin.site.urls),
153
203
  path("i18n/setlang/", csrf_exempt(set_language), name="set_language"),
154
- path("api/", include("core.workgroup_urls")),
155
204
  path("", include("pages.urls")),
156
205
  ]
157
206
 
207
+ if _graphql_feature_enabled():
208
+ urlpatterns.append(path("graphql/", EnergyGraphQLView.as_view(), name="graphql"))
209
+
158
210
  urlpatterns += autodiscovered_urlpatterns()
159
211
 
160
212
  if settings.DEBUG: